This commit is contained in:
Chaeil (Robert) Yun 2024-06-19 12:51:41 +02:00 committed by GitHub
commit 61c4b7950c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 230 additions and 25 deletions

View File

@ -208,6 +208,7 @@ option names are:
If you need to record the whole test suite logging calls to a file, you can pass
``--log-file=/path/to/log/file``. This log file is opened in write mode by default which
means that it will be overwritten at each run tests session.
You can specify the level of verbosity of the log file by passing ```--log-file-verbose=1``
If you'd like the file opened in append mode instead, then you can pass ``--log-file-mode=a``.
Note that relative paths for the log-file location, whether passed on the CLI or declared in a
config file, are always resolved relative to the current working directory.

View File

@ -2121,6 +2121,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
--log-cli-date-format=LOG_CLI_DATE_FORMAT
Log date format used by the logging module
--log-file=LOG_FILE Path to a file when logging will be written to
--log-file-verbose={0,1} Log file verbose
--log-file-mode={w,a}
Log file open mode
--log-file-level=LOG_FILE_LEVEL
@ -2221,6 +2222,8 @@ All the command-line flags can be obtained by running ``pytest --help``::
log_cli_date_format (string):
Default value for --log-cli-date-format
log_file (string): Default value for --log-file
log_file_verbose (int):
Default value for --log-file-verbose
log_file_mode (string):
Default value for --log-file-mode
log_file_level (string):

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1574,6 +1574,7 @@ class Config:
``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
``bool`` : ``False``
``string`` : empty string ``""``
``int`` : ``0``
If neither the ``default`` nor the ``type`` parameter is passed
while registering the configuration through
@ -1641,6 +1642,8 @@ class Config:
return value
elif type == "bool":
return _strtobool(str(value).strip())
elif type == "int":
return int(str(value).strip())
elif type == "string":
return value
elif type is None:

View File

@ -180,7 +180,7 @@ class Parser:
name: str,
help: str,
type: Optional[
Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
Literal["string", "paths", "pathlist", "args", "linelist", "bool", "int"]
] = None,
default: Any = NOT_SET,
) -> None:
@ -190,7 +190,7 @@ class Parser:
Name of the ini-variable.
:param type:
Type of the variable. Can be:
* ``int``: an integer
* ``string``: a string
* ``bool``: a boolean
* ``args``: a list of strings, separated as in a shell
@ -215,7 +215,16 @@ class Parser:
The value of ini-variables can be retrieved via a call to
:py:func:`config.getini(name) <pytest.Config.getini>`.
"""
assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool")
assert type in (
None,
"string",
"paths",
"pathlist",
"args",
"linelist",
"bool",
"int",
)
if default is NOT_SET:
default = get_ini_default_for_type(type)
@ -224,7 +233,9 @@ class Parser:
def get_ini_default_for_type(
type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]],
type: Optional[
Literal["string", "paths", "pathlist", "args", "linelist", "bool", "int"]
],
) -> Any:
"""
Used by addini to get the default value for a given ini-option type, when
@ -236,6 +247,8 @@ def get_ini_default_for_type(
return []
elif type == "bool":
return False
elif type == "int":
return 0
else:
return ""

View File

@ -299,6 +299,13 @@ def pytest_addoption(parser: Parser) -> None:
default=None,
help="Path to a file when logging will be written to",
)
add_option_ini(
"--log-file-verbose",
dest="log_file_verbose",
default=0,
type="int",
help="Log file verbose",
)
add_option_ini(
"--log-file-mode",
dest="log_file_mode",
@ -677,6 +684,8 @@ class LoggingPlugin:
if not os.path.isdir(directory):
os.makedirs(directory)
self.log_file_verbose = get_option_ini(config, "log_file_verbose")
self.log_file_mode = get_option_ini(config, "log_file_mode") or "w"
self.log_file_handler = _FileHandler(
log_file, mode=self.log_file_mode, encoding="UTF-8"
@ -837,6 +846,22 @@ class LoggingPlugin:
@hookimpl(wrapper=True)
def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
if self.log_file_verbose and int(self.log_file_verbose) >= 1:
old_log_file_formatter = self.log_file_handler.formatter
self.log_file_handler.setFormatter(logging.Formatter())
self.log_file_handler.emit(
logging.LogRecord(
"N/A",
logging.INFO,
"N/A",
0,
f"Running at {item.nodeid}",
None,
None,
)
)
self.log_file_handler.setFormatter(old_log_file_formatter)
self.log_cli_handler.set_when("setup")
empty: Dict[str, List[logging.LogRecord]] = {}

View File

@ -166,22 +166,20 @@ class ParameterSet(NamedTuple):
# Check all parameter sets have the correct number of values.
for param in parameters:
if len(param.values) != len(argnames):
# Construct a string representation of the expected parameter names
expected_argnames = ", ".join(argnames)
# Construct a string representation of the provided parameter values
provided_values = ", ".join(map(repr, param.values))
msg = (
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
" {names}\n"
"must be equal to the number of values ({values_len}):\n"
" {values}"
)
fail(
msg.format(
nodeid=nodeid,
values=param.values,
names=argnames,
names_len=len(argnames),
values_len=len(param.values),
),
pytrace=False,
f'{nodeid}: \n\n Error in parameterization for test "{func.__name__}".\n '
f"The number of specified parameters ({len(argnames)}) "
f"does not match the number of provided values ({len(param.values)}): {provided_values}. \n"
f" Please ensure that the correct number of parameter names "
f"({len(param.values)}) are separated by commas within quotes.\n\n"
f'Require more than parameter names: "{expected_argnames}"'
)
fail(msg, pytrace=False)
else:
# Empty parameter set (likely computed at runtime): create a single
# parameter set with NOTSET values, with the "empty parameter set" mark applied to it.

View File

@ -1380,8 +1380,15 @@ class Metafunc:
# num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
if num_ids != len(parametersets) and num_ids != 0:
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
# Construct a string representation of the expected parameter sets
expected_paramsets = len(parametersets)
msg = (
f"In {func_name}: \n\n"
f" Specified {expected_paramsets} parameter sets with {num_ids} different ids. \n"
f" Ensure all subparameters are correctly grouped within a single set of quotation marks."
)
fail(msg, pytrace=False)
return list(itertools.islice(ids, num_ids))
@ -1411,8 +1418,24 @@ class Metafunc:
arg_directness = dict.fromkeys(argnames, "direct")
for arg in indirect:
if arg not in argnames:
# Construct a list of valid parameter names
valid_params = ", ".join([f'"{name}"' for name in argnames])
# Construct a string representing the expected number of parameters
expected_param_count = len(indirect[0])
# Construct a string representing the actual number of parameters provided
actual_param_count = len(argnames)
fail(
f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist",
f"In function {self.function.__name__}: {argnames} is not a valid parameter. \n"
f"Expected {expected_param_count} sub parameters, "
f"but only {actual_param_count} were provided. \n\n"
f"Make sure to pass parameter names as strings without quotes, separated by commas, \n "
f"e.g., '@pytest.mark.parametrize({valid_params}, <Input Values>)'"
f"\n\n"
f"Or if multiple parameters are used, separate them by commas. \n "
f"e.g., '@pytest.mark.parametrize(\"arg1, arg2\", <Input Tuples>)'",
pytrace=False,
)
arg_directness[arg] = "indirect"

View File

@ -0,0 +1,2 @@
def add(a: int, b: int) -> int:
return a + b

View File

@ -0,0 +1,16 @@
import pytest
@pytest.mark.parametrize("arg1, arg2", [(1, 1)])
def test_parametrization(arg1: int, arg2: int) -> None:
assert arg1 == arg2
assert arg1 + 1 == arg2 + 1
# Gets the error: In test_parametrization: indirect fixture '(1, 1)' doesn't exist
# Goal is to change this message into a more beginner friendly message.
# The error message lives in this path: pytest/src/_pytest/python.py
# ("arg1", "arg2"), and "arg1, arg2" works, but cannot put in default parameters as normal function

View File

@ -0,0 +1,13 @@
# test_math_operations.py
import add_function
import pytest
result_index = [(1, 2, 3), (-1, 1, 0), (0, 0, 0), (5, -3, 2), (1, 1, 2)]
@pytest.mark.parametrize("a, b, expected_result", result_index)
def test_add(a: int, b: int, expected_result: int) -> None:
assert add_function.add(a, b) == expected_result

76
testing/test_log_file.py Normal file
View File

@ -0,0 +1,76 @@
import os
from _pytest.pytester import Pytester
def test_log_file_verbose(pytester: Pytester) -> None:
log_file = str(pytester.path.joinpath("pytest.log"))
pytester.makepyfile(
"""
import logging
def test_verbose1():
logging.info("test 1")
def test_verbose2():
logging.warning("test 2")
"""
)
result = pytester.runpytest(
"-s", f"--log-file={log_file}", "--log-file-verbose=1", "--log-file-level=INFO"
)
rec = pytester.inline_run()
rec.assertoutcome(passed=2)
# make sure that we get a '0' exit code for the testsuite
assert result.ret == 0
assert os.path.isfile(log_file)
with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read()
for s in [
"Running at test_log_file_verbose.py::test_verbose1",
"test 1",
"Running at test_log_file_verbose.py::test_verbose2",
"test 2",
]:
assert s in contents
def test_log_file_verbose0(pytester: Pytester) -> None:
log_file = str(pytester.path.joinpath("pytest.log"))
pytester.makepyfile(
"""
import logging
def test_verbose1():
logging.info("test 1")
def test_verbose2():
logging.warning("test 2")
"""
)
result = pytester.runpytest(
"-s", f"--log-file={log_file}", "--log-file-verbose=0", "--log-file-level=INFO"
)
rec = pytester.inline_run()
rec.assertoutcome(passed=2)
# make sure that we get a '0' exit code for the testsuite
assert result.ret == 0
assert os.path.isfile(log_file)
with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read()
for s in ["test 1", "test 2"]:
assert s in contents
for s in [
"Running at test_log_file_verbose0.py::test_verbose1",
"Running at test_log_file_verbose.py::test_verbose2",
]:
assert s not in contents

View File

@ -404,6 +404,35 @@ def test_parametrized_collected_from_command_line(pytester: Pytester) -> None:
rec.assertoutcome(passed=3)
def test_parametrized_collect_with_wrong_format(pytester: Pytester) -> None:
"""Parametrized test argument format not intuitive
line issue#8593"""
py_file = pytester.makepyfile(
"""
import pytest
@pytest.mark.parametrize("arg1", "arg2", [(1, 1)])
def test_parametrization(arg1: int, arg2: int) -> None:
assert arg1 == arg2
assert arg1 + 1 == arg2 + 1
"""
)
result = pytester.runpytest(py_file)
result.stdout.fnmatch_lines(
[
"In function test_parametrization: ['arg1'] is not a valid parameter. ",
"Expected 2 sub parameters, but only 1 were provided. ",
"",
"Make sure to pass parameter names as strings without quotes, separated by commas, ",
" e.g., '@pytest.mark.parametrize(\"arg1\", <Input Values>)'",
"",
"Or if multiple parameters are used, separate them by commas. ",
" e.g., '@pytest.mark.parametrize(\"arg1, arg2\", <Input Tuples>)'",
]
)
def test_parametrized_collect_with_wrong_args(pytester: Pytester) -> None:
"""Test collect parametrized func with wrong number of args."""
py_file = pytester.makepyfile(
@ -419,10 +448,13 @@ def test_parametrized_collect_with_wrong_args(pytester: Pytester) -> None:
result = pytester.runpytest(py_file)
result.stdout.fnmatch_lines(
[
'test_parametrized_collect_with_wrong_args.py::test_func: in "parametrize" the number of names (2):',
" ['foo', 'bar']",
"must be equal to the number of values (3):",
" (1, 2, 3)",
"test_parametrized_collect_with_wrong_args.py::test_func: ",
"",
' Error in parameterization for test "test_func".',
" The number of specified parameters (2) does not match the number of provided values (3): 1, 2, 3. ",
" Please ensure that the correct number of parameter names (3) are separated by commas within quotes.",
"",
'Require more than parameter names: "foo, bar"',
]
)