Skip to content

Commit

Permalink
Fixed placements of the warnings to be in fixture, changed warning me…
Browse files Browse the repository at this point in the history
…ssage from using string concat to f-strings for more readability, and added a function in compat to grab default parameters and its values
  • Loading branch information
huangbenny committed Dec 9, 2024
1 parent 2d991b5 commit 8b4bd3a
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 14 deletions.
15 changes: 15 additions & 0 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]:
and p.default is not Parameter.empty
)

# def check_default_arguments(func, nodeid):
# """Check for default arguments in the function and issue warnings."""
# sig = inspect.signature(func)
# for param in sig.parameters.values():
# if param.default is not param.empty:
# function_name = func.__name__
# async_default_arg_warn(nodeid, function_name, param)

def get_default_name_val(function: Callable[..., Any]) -> dict[str, Any]:
sig = signature(function)
return {
param.name: param.default
for param in sig.parameters.values()
if param.default is not Parameter.empty
}

_non_printable_ascii_translate_table = {
i: f"\\x{i:02x}" for i in range(128) if i not in range(32, 127)
Expand Down
22 changes: 21 additions & 1 deletion src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import _PytestWrapper
from _pytest.compat import get_default_name_val
from _pytest.compat import assert_never
from _pytest.compat import get_real_func
from _pytest.compat import get_real_method
Expand Down Expand Up @@ -70,7 +71,7 @@
from _pytest.scope import _ScopeName
from _pytest.scope import HIGH_SCOPES
from _pytest.scope import Scope
from _pytest.warning_types import PytestRemovedIn9Warning
from _pytest.warning_types import PytestDefaultArgumentWarning, PytestRemovedIn9Warning
from _pytest.warning_types import PytestWarning


Expand Down Expand Up @@ -1448,6 +1449,20 @@ def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]:
return tuple(dict.fromkeys(name for seq in seqs for name in seq))


def default_arg_warn(nodeid: str, function_name, param_name, param_val) -> None:
msg = (
f"Test function '{function_name}' has a default argument '{param_name}={param_val}'.\n"
)
warnings.simplefilter("always", PytestDefaultArgumentWarning)
warnings.warn(PytestDefaultArgumentWarning(msg))


def check_default_arguments(func_name, default_args, nodeid):
"""Check for default arguments in the function and issue warnings."""
for arg_name, default_val in default_args.items():
default_arg_warn(nodeid, func_name, arg_name, default_val)


class FixtureManager:
"""pytest fixture definitions and information is stored and managed
from this class.
Expand Down Expand Up @@ -1528,6 +1543,11 @@ def getfixtureinfo(
ignore_args=direct_parametrize_args,
)

if func is not None:
function_name = func.__name__
default_args = get_default_name_val(func)
check_default_arguments(function_name, default_args, node.nodeid)

return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)

def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None:
Expand Down
13 changes: 0 additions & 13 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,9 @@ def async_fail(nodeid: str) -> None:
fail(msg, pytrace=False)


def async_default_arg_warn(nodeid: str, function_name, param) -> None:
msg = (
"Test function '" + function_name + "' has a default argument '" + param.name + "=" + str(param.default) + "'.\n"
)
warnings.simplefilter("always", PytestDefaultArgumentWarning)
warnings.warn(PytestDefaultArgumentWarning(msg))


@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
testfunction = pyfuncitem.obj
sig = inspect.signature(testfunction)
for param in sig.parameters.values():
if param.default is not param.empty:
function_name = testfunction.__name__
async_default_arg_warn(pyfuncitem.nodeid, function_name, param)

if is_async_function(testfunction):
async_fail(pyfuncitem.nodeid)
Expand Down
52 changes: 52 additions & 0 deletions testing/test_default_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from _pytest.pytester import Pytester

def test_no_default_argument(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_with_default_param(param):
assert param == 42
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines([
"*fixture 'param' not found*"
])


def test_default_argument_warning(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_with_default_param(param=42):
assert param == 42
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines([
"*PytestDefaultArgumentWarning: Test function 'test_with_default_param' has a default argument 'param=42'.*"
])


def test_no_warning_for_no_default_param(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_without_default_param(param):
assert param is None
"""
)
result = pytester.runpytest()
assert "PytestDefaultArgumentWarning" not in result.stdout.str()


def test_warning_for_multiple_default_params(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_with_multiple_defaults(param1=42, param2="default"):
assert param1 == 42
assert param2 == "default"
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines([
"*PytestDefaultArgumentWarning: Test function 'test_with_multiple_defaults' has a default argument 'param1=42'.*",
"*PytestDefaultArgumentWarning: Test function 'test_with_multiple_defaults' has a default argument 'param2=default'.*"
])

0 comments on commit 8b4bd3a

Please sign in to comment.