Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/Improvement-move-add-pseudo-fu…
Browse files Browse the repository at this point in the history
…ncarg-to-metafunc-parametrize' into Improvement-move-add-pseudo-funcarg-to-metafunc-parametrize
  • Loading branch information
sadra-barikbin committed Jul 19, 2023
2 parents 3f38901 + b16079a commit 4b6d2fb
Show file tree
Hide file tree
Showing 25 changed files with 197 additions and 106 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
name: Packages
path: dist
- name: Publish package to PyPI
uses: pypa/[email protected].7
uses: pypa/[email protected].8

release-notes:

Expand Down
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ Ian Bicking
Ian Lesperance
Ilya Konstantinov
Ionuț Turturică
Isaac Virshup
Itxaso Aizpurua
Iwan Briquemont
Jaap Broekhuizen
Expand Down
1 change: 1 addition & 0 deletions changelog/11216.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If a test is skipped from inside an :ref:`xunit setup fixture <classic xunit>`, the test summary now shows the test location instead of the fixture location.
1 change: 1 addition & 0 deletions changelog/11227.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 <https://peps.python.org/pep-0678/>` ``__notes__``.
7 changes: 6 additions & 1 deletion src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,12 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
If it matches `True` is returned, otherwise an `AssertionError` is raised.
"""
__tracebackhide__ = True
value = str(self.value)
value = "\n".join(
[
str(self.value),
*getattr(self.value, "__notes__", []),
]
)
msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
if regexp == value:
msg += "\n Did you mean to `re.escape()` the regex?"
Expand Down
4 changes: 1 addition & 3 deletions src/_pytest/_py/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@
from typing import Any
from typing import Callable
from typing import cast
from typing import Literal
from typing import overload
from typing import TYPE_CHECKING

from . import error

if TYPE_CHECKING:
from typing import Literal

# Moved from local.py.
iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")

Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def num_mock_patch_args(function) -> int:


def getfuncargnames(
function: Callable[..., Any],
function: Callable[..., object],
*,
name: str = "",
is_method: bool = False,
Expand Down
7 changes: 2 additions & 5 deletions src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
from typing import Dict
from typing import final
from typing import List
from typing import Literal
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import _pytest._io
Expand All @@ -24,9 +24,6 @@
from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE
from _pytest.deprecated import check_ispytest

if TYPE_CHECKING:
from typing_extensions import Literal

FILE_OR_DIR = "file_or_dir"


Expand Down Expand Up @@ -177,7 +174,7 @@ def addini(
name: str,
help: str,
type: Optional[
"Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']"
Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
] = None,
default: Any = None,
) -> None:
Expand Down
77 changes: 50 additions & 27 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@
from _pytest.outcomes import TEST_OUTCOME
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.scope import _ScopeName
from _pytest.scope import HIGH_SCOPES
from _pytest.scope import Scope


if TYPE_CHECKING:
from typing import Deque

from _pytest.scope import _ScopeName
from _pytest.main import Session
from _pytest.python import CallSpec2
from _pytest.python import Function
Expand Down Expand Up @@ -274,16 +274,27 @@ def reorder_items_atscope(

@dataclasses.dataclass(frozen=True)
class FuncFixtureInfo:
"""Fixture-related information for a fixture-requesting item (e.g. test
function).
This is used to examine the fixtures which an item requests statically
(known during collection). This includes autouse fixtures, fixtures
requested by the `usefixtures` marker, fixtures requested in the function
parameters, and the transitive closure of these.
An item may also request fixtures dynamically (using `request.getfixturevalue`);
these are not reflected here.
"""

__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")

# Original function argument names, i.e. fixture names that the function
# requests directly.
# Fixture names that the item requests directly by function parameters.
argnames: Tuple[str, ...]
# Fixture names that the function immediately requires. These include
# Fixture names that the item immediately requires. These include
# argnames + fixture names specified via usefixtures and via autouse=True in
# fixture definitions.
initialnames: Tuple[str, ...]
# The transitive closure of the fixture names that the function requires.
# The transitive closure of the fixture names that the item requires.
# Note: can't include dynamic dependencies (`request.getfixturevalue` calls).
names_closure: List[str]
# A map from a fixture name in the transitive closure to the FixtureDefs
Expand Down Expand Up @@ -364,7 +375,7 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
self.param: Any

@property
def scope(self) -> "_ScopeName":
def scope(self) -> _ScopeName:
"""Scope string, one of "function", "class", "module", "package", "session"."""
return self._scope.value

Expand Down Expand Up @@ -467,8 +478,7 @@ def path(self) -> Path:
"""Path where the test function was collected."""
if self.scope not in ("function", "class", "module", "package"):
raise AttributeError(f"path not available in {self.scope}-scoped context")
# TODO: Remove ignore once _pyfuncitem is properly typed.
return self._pyfuncitem.path # type: ignore
return self._pyfuncitem.path

@property
def keywords(self) -> MutableMapping[str, Any]:
Expand Down Expand Up @@ -540,20 +550,17 @@ def getfixturevalue(self, argname: str) -> Any:
def _get_active_fixturedef(
self, argname: str
) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
try:
return self._fixture_defs[argname]
except KeyError:
fixturedef = self._fixture_defs.get(argname)
if fixturedef is None:
try:
fixturedef = self._getnextfixturedef(argname)
except FixtureLookupError:
if argname == "request":
cached_result = (self, [0], None)
return PseudoFixtureDef(cached_result, Scope.Function)
raise
# Remove indent to prevent the python3 exception
# from leaking into the call.
self._compute_fixture_value(fixturedef)
self._fixture_defs[argname] = fixturedef
self._compute_fixture_value(fixturedef)
self._fixture_defs[argname] = fixturedef
return fixturedef

def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
Expand Down Expand Up @@ -869,10 +876,10 @@ def _teardown_yield_fixture(fixturefunc, it) -> None:


def _eval_scope_callable(
scope_callable: "Callable[[str, Config], _ScopeName]",
scope_callable: Callable[[str, Config], _ScopeName],
fixture_name: str,
config: Config,
) -> "_ScopeName":
) -> _ScopeName:
try:
# Type ignored because there is no typing mechanism to specify
# keyword arguments, currently.
Expand Down Expand Up @@ -907,7 +914,7 @@ def __init__(
baseid: Optional[str],
argname: str,
func: "_FixtureFunc[FixtureValue]",
scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None],
params: Optional[Sequence[object]],
unittest: bool = False,
ids: Optional[
Expand Down Expand Up @@ -960,8 +967,6 @@ def __init__(
# The names requested by the fixtures.
self.argnames: Final = getfuncargnames(func, name=argname, is_method=unittest)
# Whether the fixture was collected from a unittest TestCase class.
# Note that it really only makes sense to define autouse fixtures in
# unittest TestCases.
self.unittest: Final = unittest
# If the fixture was executed, the current value of the fixture.
# Can change if the fixture is executed with different parameters.
Expand All @@ -972,7 +977,7 @@ def __init__(
self.is_pseudo = is_pseudo

@property
def scope(self) -> "_ScopeName":
def scope(self) -> _ScopeName:
"""Scope string, one of "function", "class", "module", "package", "session"."""
return self._scope.value

Expand Down Expand Up @@ -1086,9 +1091,10 @@ def pytest_fixture_setup(
try:
result = call_fixture_func(fixturefunc, request, kwargs)
except TEST_OUTCOME as e:
if isinstance(e, skip.Exception) and not fixturefunc.__name__.startswith(
"xunit_setup"
):
if isinstance(e, skip.Exception):
# The test requested a fixture which caused a skip.
# Don't show the fixture as the skip location, as then the user
# wouldn't know which test skipped.
e._use_item_location = True
fixturedef.cached_result = (None, my_cache_key, e)
raise
Expand Down Expand Up @@ -1391,8 +1397,26 @@ def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]:
return parametrize_argnames

def getfixtureinfo(
self, node: nodes.Node, func, cls, funcargs: bool = True
self,
node: nodes.Item,
func: Callable[..., object],
cls: Optional[type],
funcargs: bool = True,
) -> FuncFixtureInfo:
"""Calculate the :class:`FuncFixtureInfo` for an item.
If ``funcargs`` is false, or if the item sets an attribute
``nofuncargs = True``, then ``func`` is not examined at all.
:param node:
The item requesting the fixtures.
:param func:
The item's function.
:param cls:
If the function is a method, the method's class.
:param funcargs:
Whether to look into func's parameters as fixture requests.
"""
if funcargs and not getattr(node, "nofuncargs", False):
argnames = getfuncargnames(func, name=node.name, cls=cls)
else:
Expand All @@ -1402,8 +1426,7 @@ def getfixtureinfo(
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
)
initialnames = usefixtures + argnames
fm = node.session._fixturemanager
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
initialnames, names_closure, arg2fixturedefs = self.getfixtureclosure(
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
)
return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
if TYPE_CHECKING:
import pdb
import warnings
from typing_extensions import Literal
from typing import Literal

from _pytest._code.code import ExceptionRepr
from _pytest._code.code import ExceptionInfo
Expand Down
3 changes: 1 addition & 2 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import final
from typing import Generator
from typing import List
from typing import Literal
from typing import Mapping
from typing import Optional
from typing import Tuple
Expand All @@ -41,8 +42,6 @@

if TYPE_CHECKING:
logging_StreamHandler = logging.StreamHandler[StringIO]

from typing_extensions import Literal
else:
logging_StreamHandler = logging.StreamHandler

Expand Down
6 changes: 1 addition & 5 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
from typing import FrozenSet
from typing import Iterator
from typing import List
from typing import Literal
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import Union

import _pytest._code
Expand All @@ -43,10 +43,6 @@
from _pytest.runner import SetupState


if TYPE_CHECKING:
from typing_extensions import Literal


def pytest_addoption(parser: Parser) -> None:
parser.addini(
"norecursedirs",
Expand Down
10 changes: 3 additions & 7 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Literal
from typing import Mapping
from typing import Optional
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union

import _pytest
Expand Down Expand Up @@ -78,17 +78,13 @@
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import parts
from _pytest.pathlib import visit
from _pytest.scope import _ScopeName
from _pytest.scope import Scope
from _pytest.stash import StashKey
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestReturnNotNoneWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning

if TYPE_CHECKING:
from typing_extensions import Literal

from _pytest.scope import _ScopeName


_PYTEST_DIR = Path(_pytest.__file__).parent

Expand Down Expand Up @@ -1281,7 +1277,7 @@ def parametrize(
ids: Optional[
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
] = None,
scope: "Optional[_ScopeName]" = None,
scope: Optional[_ScopeName] = None,
*,
_param_mark: Optional[Mark] = None,
) -> None:
Expand Down
8 changes: 8 additions & 0 deletions src/_pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,14 @@ def raises( # noqa: F811
>>> with pytest.raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
The ``match`` argument searches the formatted exception string, which includes any
`PEP-678 <https://peps.python.org/pep-0678/>` ``__notes__``:
>>> with pytest.raises(ValueError, match=r'had a note added'): # doctest: +SKIP
... e = ValueError("value must be 42")
... e.add_note("had a note added")
... raise e
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
details of the captured exception::
Expand Down
Loading

0 comments on commit 4b6d2fb

Please sign in to comment.