Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ def find_spec(self, name, path=None, target=None):
# there's nothing to rewrite there
# python3.5 - python3.6: `namespace`
# python3.7+: `None`
or spec.origin in {None, "namespace"}
or spec.origin == "namespace"
or spec.origin is None
# we can only rewrite source files
or not isinstance(spec.loader, importlib.machinery.SourceFileLoader)
# if the file doesn't exist, we can't rewrite it
Expand Down Expand Up @@ -743,8 +744,7 @@ def visit_Assert(self, assert_):
from _pytest.warning_types import PytestAssertRewriteWarning
import warnings

# Ignore type: typeshed bug https://github.com/python/typeshed/pull/3121
warnings.warn_explicit( # type: ignore
warnings.warn_explicit(
PytestAssertRewriteWarning(
"assertion is always true, perhaps remove parentheses?"
),
Expand Down
12 changes: 9 additions & 3 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import json
import os
from collections import OrderedDict
from typing import List

import attr
import py
Expand All @@ -15,6 +16,9 @@
from .pathlib import Path
from .pathlib import resolve_from_str
from .pathlib import rm_rf
from _pytest import nodes
from _pytest.config import Config
from _pytest.main import Session

README_CONTENT = """\
# pytest cache directory #
Expand Down Expand Up @@ -263,10 +267,12 @@ def __init__(self, config):
self.active = config.option.newfirst
self.cached_nodeids = config.cache.get("cache/nodeids", [])

def pytest_collection_modifyitems(self, session, config, items):
def pytest_collection_modifyitems(
self, session: Session, config: Config, items: List[nodes.Item]
) -> None:
if self.active:
new_items = OrderedDict()
other_items = OrderedDict()
new_items = OrderedDict() # type: OrderedDict[str, nodes.Item]
other_items = OrderedDict() # type: OrderedDict[str, nodes.Item]
for item in items:
if item.nodeid not in self.cached_nodeids:
new_items[item.nodeid] = item
Expand Down
10 changes: 5 additions & 5 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import pytest
from _pytest.compat import CaptureIO
from _pytest.fixtures import FixtureRequest

patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}

Expand Down Expand Up @@ -241,13 +242,12 @@ def pytest_internalerror(self, excinfo):
capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}


def _ensure_only_one_capture_fixture(request, name):
fixtures = set(request.fixturenames) & capture_fixtures - {name}
def _ensure_only_one_capture_fixture(request: FixtureRequest, name):
fixtures = sorted(set(request.fixturenames) & capture_fixtures - {name})
if fixtures:
fixtures = sorted(fixtures)
fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
arg = fixtures[0] if len(fixtures) == 1 else fixtures
raise request.raiseerror(
"cannot use {} and {} at the same time".format(fixtures, name)
"cannot use {} and {} at the same time".format(arg, name)
)


Expand Down
91 changes: 58 additions & 33 deletions src/_pytest/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
import traceback
import warnings
from contextlib import contextmanager
from typing import Dict
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union

import pytest
from _pytest import outcomes
Expand All @@ -20,6 +24,10 @@
from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning

if False: # TYPE_CHECKING
import doctest
from typing import Type

DOCTEST_REPORT_CHOICE_NONE = "none"
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
Expand All @@ -36,6 +44,8 @@

# Lazy definition of runner class
RUNNER_CLASS = None
# Lazy definition of output checker class
CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]]


def pytest_addoption(parser):
Expand Down Expand Up @@ -139,7 +149,7 @@ def __init__(self, failures):
self.failures = failures


def _init_runner_class():
def _init_runner_class() -> "Type[doctest.DocTestRunner]":
import doctest

class PytestDoctestRunner(doctest.DebugRunner):
Expand Down Expand Up @@ -177,12 +187,19 @@ def report_unexpected_exception(self, out, test, example, exc_info):
return PytestDoctestRunner


def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=True):
def _get_runner(
checker: Optional["doctest.OutputChecker"] = None,
verbose: Optional[bool] = None,
optionflags: int = 0,
continue_on_failure: bool = True,
) -> "doctest.DocTestRunner":
# We need this in order to do a lazy import on doctest
global RUNNER_CLASS
if RUNNER_CLASS is None:
RUNNER_CLASS = _init_runner_class()
return RUNNER_CLASS(
# Type ignored because the continue_on_failure argument is only defined on
# PytestDoctestRunner, which is lazily defined so can't be used as a type.
return RUNNER_CLASS( # type: ignore
checker=checker,
verbose=verbose,
optionflags=optionflags,
Expand Down Expand Up @@ -211,7 +228,7 @@ def setup(self):
def runtest(self):
_check_all_skipped(self.dtest)
self._disable_output_capturing_for_darwin()
failures = []
failures = [] # type: List[doctest.DocTestFailure]
self.runner.run(self.dtest, out=failures)
if failures:
raise MultipleDoctestFailures(failures)
Expand All @@ -232,7 +249,9 @@ def _disable_output_capturing_for_darwin(self):
def repr_failure(self, excinfo):
import doctest

failures = None
failures = (
None
) # type: Optional[List[Union[doctest.DocTestFailure, doctest.UnexpectedException]]]
if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)):
failures = [excinfo.value]
elif excinfo.errisinstance(MultipleDoctestFailures):
Expand All @@ -255,8 +274,10 @@ def repr_failure(self, excinfo):
self.config.getoption("doctestreport")
)
if lineno is not None:
assert failure.test.docstring is not None
lines = failure.test.docstring.splitlines(False)
# add line numbers to the left of the error message
assert test.lineno is not None
lines = [
"%03d %s" % (i + test.lineno + 1, x)
for (i, x) in enumerate(lines)
Expand Down Expand Up @@ -288,7 +309,7 @@ def reportinfo(self):
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name


def _get_flag_lookup():
def _get_flag_lookup() -> Dict[str, int]:
import doctest

return dict(
Expand Down Expand Up @@ -340,7 +361,7 @@ def collect(self):
optionflags = get_optionflags(self)

runner = _get_runner(
verbose=0,
verbose=False,
optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config),
Expand Down Expand Up @@ -419,7 +440,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
return
with _patch_unwrap_mock_aware():

doctest.DocTestFinder._find(
# Type ignored because this is a private function.
doctest.DocTestFinder._find( # type: ignore
self, tests, obj, name, module, source_lines, globs, seen
)

Expand All @@ -437,7 +459,7 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen):
finder = MockAwareDocTestFinder()
optionflags = get_optionflags(self)
runner = _get_runner(
verbose=0,
verbose=False,
optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config),
Expand Down Expand Up @@ -466,24 +488,7 @@ def func():
return fixture_request


def _get_checker():
"""
Returns a doctest.OutputChecker subclass that supports some
additional options:

* ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
prefixes (respectively) in string literals. Useful when the same
doctest should run in Python 2 and Python 3.

* NUMBER to ignore floating-point differences smaller than the
precision of the literal number in the doctest.

An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_checker, "LiteralsOutputChecker"):
return _get_checker.LiteralsOutputChecker()

def _init_checker_class() -> "Type[doctest.OutputChecker]":
import doctest
import re

Expand Down Expand Up @@ -573,11 +578,31 @@ def _remove_unwanted_precision(self, want, got):
offset += w.end() - w.start() - (g.end() - g.start())
return got

_get_checker.LiteralsOutputChecker = LiteralsOutputChecker
return _get_checker.LiteralsOutputChecker()
return LiteralsOutputChecker


def _get_checker() -> "doctest.OutputChecker":
"""
Returns a doctest.OutputChecker subclass that supports some
additional options:

* ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
prefixes (respectively) in string literals. Useful when the same
doctest should run in Python 2 and Python 3.

* NUMBER to ignore floating-point differences smaller than the
precision of the literal number in the doctest.

An inner class is used to avoid importing "doctest" at the module
level.
"""
global CHECKER_CLASS
if CHECKER_CLASS is None:
CHECKER_CLASS = _init_checker_class()
return CHECKER_CLASS()


def _get_allow_unicode_flag():
def _get_allow_unicode_flag() -> int:
"""
Registers and returns the ALLOW_UNICODE flag.
"""
Expand All @@ -586,7 +611,7 @@ def _get_allow_unicode_flag():
return doctest.register_optionflag("ALLOW_UNICODE")


def _get_allow_bytes_flag():
def _get_allow_bytes_flag() -> int:
"""
Registers and returns the ALLOW_BYTES flag.
"""
Expand All @@ -595,7 +620,7 @@ def _get_allow_bytes_flag():
return doctest.register_optionflag("ALLOW_BYTES")


def _get_number_flag():
def _get_number_flag() -> int:
"""
Registers and returns the NUMBER flag.
"""
Expand All @@ -604,7 +629,7 @@ def _get_number_flag():
return doctest.register_optionflag("NUMBER")


def _get_report_choice(key):
def _get_report_choice(key: str) -> int:
"""
This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.
Expand Down
27 changes: 16 additions & 11 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import logging
import re
from contextlib import contextmanager
from typing import AbstractSet
from typing import Dict
from typing import List
from typing import Mapping

import py

Expand Down Expand Up @@ -32,14 +36,15 @@ class ColoredLevelFormatter(logging.Formatter):
logging.INFO: {"green"},
logging.DEBUG: {"purple"},
logging.NOTSET: set(),
}
} # type: Mapping[int, AbstractSet[str]]
LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*s)")

def __init__(self, terminalwriter, *args, **kwargs):
def __init__(self, terminalwriter, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._original_fmt = self._style._fmt
self._level_to_fmt_mapping = {}
self._level_to_fmt_mapping = {} # type: Dict[int, str]

assert self._fmt is not None
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
if not levelname_fmt_match:
return
Expand Down Expand Up @@ -216,31 +221,31 @@ def catching_logs(handler, formatter=None, level=None):
class LogCaptureHandler(logging.StreamHandler):
"""A logging handler that stores log records and the log text."""

def __init__(self):
def __init__(self) -> None:
"""Creates a new log handler."""
logging.StreamHandler.__init__(self, py.io.TextIO())
self.records = []
self.records = [] # type: List[logging.LogRecord]

def emit(self, record):
def emit(self, record: logging.LogRecord) -> None:
"""Keep the log records in a list in addition to the log text."""
self.records.append(record)
logging.StreamHandler.emit(self, record)

def reset(self):
def reset(self) -> None:
self.records = []
self.stream = py.io.TextIO()


class LogCaptureFixture:
"""Provides access and control of log capturing."""

def __init__(self, item):
def __init__(self, item) -> None:
"""Creates a new funcarg."""
self._item = item
# dict of log name -> log level
self._initial_log_levels = {} # Dict[str, int]
self._initial_log_levels = {} # type: Dict[str, int]

def _finalize(self):
def _finalize(self) -> None:
"""Finalizes the fixture.

This restores the log levels changed by :meth:`set_level`.
Expand Down Expand Up @@ -453,7 +458,7 @@ def _create_formatter(self, log_format, log_date_format):
):
formatter = ColoredLevelFormatter(
create_terminal_writer(self._config), log_format, log_date_format
)
) # type: logging.Formatter
else:
formatter = logging.Formatter(log_format, log_date_format)

Expand Down
3 changes: 1 addition & 2 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ def warn(self, warning):
)
)
path, lineno = get_fslocation_from_item(self)
# Type ignored: https://github.com/python/typeshed/pull/3121
warnings.warn_explicit( # type: ignore
warnings.warn_explicit(
warning,
category=None,
filename=str(path),
Expand Down
Loading