Merge branch 'main' into warn-when-a-mark-is-applied-to-a-fixture

This commit is contained in:
Thomas Grainger
2022-10-09 20:10:53 +01:00
committed by GitHub
296 changed files with 23313 additions and 9657 deletions

View File

@@ -1,13 +1,15 @@
import operator
import sys
from contextlib import contextmanager
from decimal import Decimal
from fractions import Fraction
from math import sqrt
from operator import eq
from operator import ne
from typing import Optional
import pytest
from _pytest.pytester import Pytester
from _pytest.python_api import _recursive_sequence_map
from pytest import approx
inf, nan = float("inf"), float("nan")
@@ -43,7 +45,250 @@ def mocked_doctest_runner(monkeypatch):
return MyDocTestRunner()
@contextmanager
def temporary_verbosity(config, verbosity=0):
original_verbosity = config.getoption("verbose")
config.option.verbose = verbosity
try:
yield
finally:
config.option.verbose = original_verbosity
@pytest.fixture
def assert_approx_raises_regex(pytestconfig):
def do_assert(lhs, rhs, expected_message, verbosity_level=0):
import re
with temporary_verbosity(pytestconfig, verbosity_level):
with pytest.raises(AssertionError) as e:
assert lhs == approx(rhs)
nl = "\n"
obtained_message = str(e.value).splitlines()[1:]
assert len(obtained_message) == len(expected_message), (
"Regex message length doesn't match obtained.\n"
"Obtained:\n"
f"{nl.join(obtained_message)}\n\n"
"Expected regex:\n"
f"{nl.join(expected_message)}\n\n"
)
for i, (obtained_line, expected_line) in enumerate(
zip(obtained_message, expected_message)
):
regex = re.compile(expected_line)
assert regex.match(obtained_line) is not None, (
"Unexpected error message:\n"
f"{nl.join(obtained_message)}\n\n"
"Did not match regex:\n"
f"{nl.join(expected_message)}\n\n"
f"With verbosity level = {verbosity_level}, on line {i}"
)
return do_assert
SOME_FLOAT = r"[+-]?([0-9]*[.])?[0-9]+\s*"
SOME_INT = r"[0-9]+\s*"
class TestApprox:
def test_error_messages_native_dtypes(self, assert_approx_raises_regex):
assert_approx_raises_regex(
2.0,
1.0,
[
" comparison failed",
f" Obtained: {SOME_FLOAT}",
f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
{"a": 1.0, "b": 1000.0, "c": 1000000.0},
{
"a": 2.0,
"b": 1000.0,
"c": 3000000.0,
},
[
r" comparison failed. Mismatched elements: 2 / 3:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected ",
rf" a \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" c \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
[1.0, 2.0, 3.0, 4.0],
[1.0, 3.0, 3.0, 5.0],
[
r" comparison failed. Mismatched elements: 2 / 4:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected ",
rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" 3 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
(1, 2.2, 4),
(1, 3.2, 4),
[
r" comparison failed. Mismatched elements: 1 / 3:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected ",
rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
# Specific test for comparison with 0.0 (relative diff will be 'inf')
assert_approx_raises_regex(
[0.0],
[1.0],
[
r" comparison failed. Mismatched elements: 1 / 1:",
rf" Max absolute difference: {SOME_FLOAT}",
r" Max relative difference: inf",
r" Index \| Obtained\s+\| Expected ",
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
a = np.linspace(0, 100, 20)
b = np.linspace(0, 100, 20)
a[10] += 0.5
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 1 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
rf" \(10,\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
assert_approx_raises_regex(
np.array(
[
[[1.1987311, 12412342.3], [3.214143244, 1423412423415.677]],
[[1, 2], [3, 219371297321973]],
]
),
np.array(
[
[[1.12313, 12412342.3], [3.214143244, 534523542345.677]],
[[1, 2], [3, 7]],
]
),
[
r" comparison failed. Mismatched elements: 3 / 8:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index\s+\| Obtained\s+\| Expected\s+",
rf" \(0, 0, 0\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(0, 1, 1\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(1, 1, 1\) \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
# Specific test for comparison with 0.0 (relative diff will be 'inf')
assert_approx_raises_regex(
np.array([0.0]),
np.array([1.0]),
[
r" comparison failed. Mismatched elements: 1 / 1:",
rf" Max absolute difference: {SOME_FLOAT}",
r" Max relative difference: inf",
r" Index \| Obtained\s+\| Expected ",
rf"\s*\(0,\)\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
def test_error_messages_invalid_args(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
with pytest.raises(AssertionError) as e:
assert np.array([[1.2, 3.4], [4.0, 5.0]]) == pytest.approx(
np.array([[4.0], [5.0]])
)
message = "\n".join(str(e.value).split("\n")[1:])
assert message == "\n".join(
[
" Impossible to compare arrays with different shapes.",
" Shapes: (2, 1) and (2, 2)",
]
)
with pytest.raises(AssertionError) as e:
assert [1.0, 2.0, 3.0] == pytest.approx([4.0, 5.0])
message = "\n".join(str(e.value).split("\n")[1:])
assert message == "\n".join(
[
" Impossible to compare lists with different sizes.",
" Lengths: 2 and 3",
]
)
def test_error_messages_with_different_verbosity(self, assert_approx_raises_regex):
np = pytest.importorskip("numpy")
for v in [0, 1, 2]:
# Verbosity level doesn't affect the error message for scalars
assert_approx_raises_regex(
2.0,
1.0,
[
" comparison failed",
f" Obtained: {SOME_FLOAT}",
f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
],
verbosity_level=v,
)
a = np.linspace(1, 101, 20)
b = np.linspace(2, 102, 20)
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 20 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
rf" \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
rf" \(2,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}...",
"",
rf"\s*...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show",
],
verbosity_level=0,
)
assert_approx_raises_regex(
a,
b,
[
r" comparison failed. Mismatched elements: 20 / 20:",
rf" Max absolute difference: {SOME_FLOAT}",
rf" Max relative difference: {SOME_FLOAT}",
r" Index \| Obtained\s+\| Expected",
]
+ [
rf" \({i},\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}"
for i in range(20)
],
verbosity_level=2,
)
def test_repr_string(self):
assert repr(approx(1.0)) == "1.0 ± 1.0e-06"
assert repr(approx([1.0, 2.0])) == "approx([1.0 ± 1.0e-06, 2.0 ± 2.0e-06])"
@@ -89,6 +334,12 @@ class TestApprox:
np_array = np.array(value)
assert repr(approx(np_array)) == expected_repr_string
def test_bool(self):
with pytest.raises(AssertionError) as err:
assert approx(1)
assert err.match(r"approx\(\) is not supported in a boolean context")
def test_operator_overloading(self):
assert 1 == approx(1, rel=1e-6, abs=1e-12)
assert not (1 != approx(1, rel=1e-6, abs=1e-12))
@@ -536,7 +787,7 @@ class TestApprox:
def test_expected_value_type_error(self, x, name):
with pytest.raises(
TypeError,
match=fr"pytest.approx\(\) does not support nested {name}:",
match=rf"pytest.approx\(\) does not support nested {name}:",
):
approx(x)
@@ -574,7 +825,6 @@ class TestApprox:
assert 1.0 != approx([None])
assert None != approx([1.0]) # noqa: E711
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires ordered dicts")
def test_nonnumeric_dict_repr(self):
"""Dicts with non-numerics and infinites have no tolerances"""
x1 = {"foo": 1.0000005, "bar": None, "foobar": inf}
@@ -624,13 +874,49 @@ class TestApprox:
assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual
def test_generic_sized_iterable_object(self):
class MySizedIterable:
def __iter__(self):
return iter([1, 2, 3, 4])
def test_generic_ordered_sequence(self):
class MySequence:
def __getitem__(self, i):
return [1, 2, 3, 4][i]
def __len__(self):
return 4
expected = MySizedIterable()
assert [1, 2, 3, 4] == approx(expected)
expected = MySequence()
assert [1, 2, 3, 4] == approx(expected, abs=1e-4)
expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])"
assert repr(approx(expected)) == expected_repr
def test_allow_ordered_sequences_only(self) -> None:
"""pytest.approx() should raise an error on unordered sequences (#9692)."""
with pytest.raises(TypeError, match="only supports ordered sequences"):
assert {1, 2, 3} == approx({1, 2, 3})
class TestRecursiveSequenceMap:
def test_map_over_scalar(self):
assert _recursive_sequence_map(sqrt, 16) == 4
def test_map_over_empty_list(self):
assert _recursive_sequence_map(sqrt, []) == []
def test_map_over_list(self):
assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26]
def test_map_over_tuple(self):
assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26)
def test_map_over_nested_lists(self):
assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [
2,
[5, 8],
[[7]],
]
def test_map_over_mixed_sequence(self):
assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [
2,
(5, 8),
[(7)],
]

View File

@@ -7,11 +7,12 @@ from typing import Dict
import _pytest._code
import pytest
from _pytest.config import ExitCode
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.nodes import Collector
from _pytest.pytester import Pytester
from _pytest.python import Class
from _pytest.python import Instance
from _pytest.python import Function
class TestModule:
@@ -294,7 +295,7 @@ class TestFunction:
from _pytest.fixtures import FixtureManager
config = pytester.parseconfigure()
session = pytester.Session.from_config(config)
session = Session.from_config(config)
session._fixturemanager = FixtureManager(session)
return pytest.Function.from_parent(parent=session, **kwargs)
@@ -584,7 +585,7 @@ class TestFunction:
pass
"""
)
colitems = modcol.collect()[0].collect()[0].collect()
colitems = modcol.collect()[0].collect()
assert colitems[0].name == "test1[a-c]"
assert colitems[1].name == "test1[b-c]"
assert colitems[2].name == "test2[a-c]"
@@ -770,6 +771,36 @@ class TestSorting:
assert len(colitems) == 2
assert [item.name for item in colitems] == ["test_b", "test_a"]
def test_ordered_by_definition_order(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""\
class Test1:
def test_foo(): pass
def test_bar(): pass
class Test2:
def test_foo(): pass
test_bar = Test1.test_bar
class Test3(Test2):
def test_baz(): pass
"""
)
result = pytester.runpytest("--collect-only")
result.stdout.fnmatch_lines(
[
"*Class Test1*",
"*Function test_foo*",
"*Function test_bar*",
"*Class Test2*",
# previously the order was flipped due to Test1.test_bar reference
"*Function test_foo*",
"*Function test_bar*",
"*Class Test3*",
"*Function test_foo*",
"*Function test_bar*",
"*Function test_baz*",
]
)
class TestConftestCustomization:
def test_pytest_pycollect_module(self, pytester: Pytester) -> None:
@@ -778,9 +809,9 @@ class TestConftestCustomization:
import pytest
class MyModule(pytest.Module):
pass
def pytest_pycollect_makemodule(fspath, parent):
if fspath.name == "test_xyz.py":
return MyModule.from_parent(path=fspath, parent=parent)
def pytest_pycollect_makemodule(module_path, parent):
if module_path.name == "test_xyz.py":
return MyModule.from_parent(path=module_path, parent=parent)
"""
)
pytester.makepyfile("def test_some(): pass")
@@ -882,9 +913,9 @@ class TestConftestCustomization:
return Loader()
sys.meta_path.append(Finder())
def pytest_collect_file(fspath, parent):
if fspath.suffix == ".narf":
return Module.from_parent(path=fspath, parent=parent)"""
def pytest_collect_file(file_path, parent):
if file_path.suffix == ".narf":
return Module.from_parent(path=file_path, parent=parent)"""
)
pytester.makefile(
".narf",
@@ -1124,8 +1155,8 @@ class TestReportInfo:
def test_func_reportinfo(self, pytester: Pytester) -> None:
item = pytester.getitem("def test_func(): pass")
fspath, lineno, modpath = item.reportinfo()
assert str(fspath) == str(item.path)
path, lineno, modpath = item.reportinfo()
assert os.fspath(path) == str(item.path)
assert lineno == 0
assert modpath == "test_func"
@@ -1139,8 +1170,8 @@ class TestReportInfo:
)
classcol = pytester.collect_by_name(modcol, "TestClass")
assert isinstance(classcol, Class)
fspath, lineno, msg = classcol.reportinfo()
assert str(fspath) == str(modcol.path)
path, lineno, msg = classcol.reportinfo()
assert os.fspath(path) == str(modcol.path)
assert lineno == 1
assert msg == "TestClass"
@@ -1152,19 +1183,26 @@ class TestReportInfo:
modcol = pytester.getmodulecol(
"""
# lineno 0
class TestClass(object):
class TestClass:
def __getattr__(self, name):
return "this is not an int"
def __class_getattr__(cls, name):
return "this is not an int"
def intest_foo(self):
pass
def test_bar(self):
pass
"""
)
classcol = pytester.collect_by_name(modcol, "TestClass")
assert isinstance(classcol, Class)
instance = list(classcol.collect())[0]
assert isinstance(instance, Instance)
fspath, lineno, msg = instance.reportinfo()
path, lineno, msg = classcol.reportinfo()
func = list(classcol.collect())[0]
assert isinstance(func, Function)
path, lineno, msg = func.reportinfo()
def test_customized_python_discovery(pytester: Pytester) -> None:
@@ -1237,7 +1275,7 @@ def test_unorderable_types(pytester: Pytester) -> None:
assert result.ret == ExitCode.NO_TESTS_COLLECTED
@pytest.mark.filterwarnings("default")
@pytest.mark.filterwarnings("default::pytest.PytestCollectionWarning")
def test_dont_collect_non_function_callable(pytester: Pytester) -> None:
"""Test for issue https://github.com/pytest-dev/pytest/issues/331

View File

@@ -103,10 +103,6 @@ def test_getfuncargnames_staticmethod_partial():
@pytest.mark.pytester_example_path("fixtures/fill_fixtures")
class TestFillFixtures:
def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility
assert pytest._fillfuncargs == fixtures._fillfuncargs
def test_funcarg_lookupfails(self, pytester: Pytester) -> None:
pytester.copy_example()
result = pytester.runpytest() # "--collect-only")
@@ -967,8 +963,6 @@ class TestRequestBasic:
(item,) = pytester.genitems([modcol])
req = fixtures.FixtureRequest(item, _ispytest=True)
assert req.path == modcol.path
with pytest.warns(pytest.PytestDeprecationWarning):
assert req.fspath == modcol.fspath
def test_request_fixturenames(self, pytester: Pytester) -> None:
pytester.makepyfile(
@@ -1094,6 +1088,20 @@ class TestRequestBasic:
reprec.assertoutcome(passed=2)
class TestRequestSessionScoped:
@pytest.fixture(scope="session")
def session_request(self, request):
return request
@pytest.mark.parametrize("name", ["path", "module"])
def test_session_scoped_unavailable_attributes(self, session_request, name):
with pytest.raises(
AttributeError,
match=f"{name} not available in session-scoped context",
):
getattr(session_request, name)
class TestRequestMarking:
def test_applymarker(self, pytester: Pytester) -> None:
item1, item2 = pytester.getitems(
@@ -2069,9 +2077,9 @@ class TestAutouseManagement:
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config
values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[
0
].values
values = config.pluginmanager._getconftestmodules(
p, importmode="prepend", rootpath=pytester.path
)[0].values
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
def test_scope_ordering(self, pytester: Pytester) -> None:
@@ -3334,9 +3342,9 @@ class TestShowFixtures:
result = pytester.runpytest("--fixtures")
result.stdout.fnmatch_lines(
[
"tmp_path_factory [[]session scope[]]",
"tmp_path_factory [[]session scope[]] -- .../_pytest/tmpdir.py:*",
"*for the test session*",
"tmp_path",
"tmp_path -- .../_pytest/tmpdir.py:*",
"*temporary directory*",
]
)
@@ -3345,9 +3353,9 @@ class TestShowFixtures:
result = pytester.runpytest("--fixtures", "-v")
result.stdout.fnmatch_lines(
[
"tmp_path_factory [[]session scope[]] -- *tmpdir.py*",
"tmp_path_factory [[]session scope[]] -- .../_pytest/tmpdir.py:*",
"*for the test session*",
"tmp_path -- *tmpdir.py*",
"tmp_path -- .../_pytest/tmpdir.py:*",
"*temporary directory*",
]
)
@@ -3367,9 +3375,9 @@ class TestShowFixtures:
result = pytester.runpytest("--fixtures", p)
result.stdout.fnmatch_lines(
"""
*tmp_path
*tmp_path -- *
*fixtures defined from*
*arg1*
*arg1 -- test_show_fixtures_testmodule.py:6*
*hello world*
"""
)
@@ -3429,10 +3437,10 @@ class TestShowFixtures:
textwrap.dedent(
"""\
* fixtures defined from test_show_fixtures_trimmed_doc *
arg2
arg2 -- test_show_fixtures_trimmed_doc.py:10
line1
line2
arg1
arg1 -- test_show_fixtures_trimmed_doc.py:3
line1
line2
"""
@@ -3458,7 +3466,7 @@ class TestShowFixtures:
textwrap.dedent(
"""\
* fixtures defined from test_show_fixtures_indented_doc *
fixture1
fixture1 -- test_show_fixtures_indented_doc.py:3
line1
indented line
"""
@@ -3486,7 +3494,7 @@ class TestShowFixtures:
textwrap.dedent(
"""\
* fixtures defined from test_show_fixtures_indented_doc_first_line_unindented *
fixture1
fixture1 -- test_show_fixtures_indented_doc_first_line_unindented.py:3
line1
line2
indented line
@@ -3514,7 +3522,7 @@ class TestShowFixtures:
textwrap.dedent(
"""\
* fixtures defined from test_show_fixtures_indented_in_class *
fixture1
fixture1 -- test_show_fixtures_indented_in_class.py:4
line1
line2
indented line
@@ -3554,11 +3562,11 @@ class TestShowFixtures:
result.stdout.fnmatch_lines(
"""
* fixtures defined from test_a *
fix_a
fix_a -- test_a.py:4
Fixture A
* fixtures defined from test_b *
fix_b
fix_b -- test_b.py:4
Fixture B
"""
)
@@ -3594,11 +3602,11 @@ class TestShowFixtures:
result.stdout.fnmatch_lines(
"""
* fixtures defined from conftest *
arg1
arg1 -- conftest.py:3
Hello World in conftest.py
* fixtures defined from test_show_fixtures_with_same_name *
arg1
arg1 -- test_show_fixtures_with_same_name.py:3
Hi from test module
"""
)

View File

@@ -1,81 +1,8 @@
from typing import Any
import pytest
from _pytest import runner
from _pytest._code import getfslineno
from _pytest.fixtures import getfixturemarker
from _pytest.pytester import Pytester
class TestOEJSKITSpecials:
def test_funcarg_non_pycollectobj(
self, pytester: Pytester, recwarn
) -> None: # rough jstests usage
pytester.makeconftest(
"""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.fspath, 3, "xyz"
"""
)
modcol = pytester.getmodulecol(
"""
import pytest
@pytest.fixture
def arg1(request):
return 42
class MyClass(object):
pass
"""
)
# this hook finds funcarg factories
rep = runner.collect_one_node(collector=modcol)
# TODO: Don't treat as Any.
clscol: Any = rep.result[0]
clscol.obj = lambda arg1: None
clscol.funcargs = {}
pytest._fillfuncargs(clscol)
assert clscol.funcargs["arg1"] == 42
def test_autouse_fixture(
self, pytester: Pytester, recwarn
) -> None: # rough jstests usage
pytester.makeconftest(
"""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.fspath, 3, "xyz"
"""
)
modcol = pytester.getmodulecol(
"""
import pytest
@pytest.fixture(autouse=True)
def hello():
pass
@pytest.fixture
def arg1(request):
return 42
class MyClass(object):
pass
"""
)
# this hook finds funcarg factories
rep = runner.collect_one_node(modcol)
# TODO: Don't treat as Any.
clscol: Any = rep.result[0]
clscol.obj = lambda: None
clscol.funcargs = {}
pytest._fillfuncargs(clscol)
assert not clscol.funcargs
from _pytest.python import Function
def test_wrapped_getfslineno() -> None:
@@ -475,3 +402,28 @@ class TestParameterize:
)
res = pytester.runpytest("--collect-only")
res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"])
def test_function_instance(pytester: Pytester) -> None:
items = pytester.getitems(
"""
def test_func(): pass
class TestIt:
def test_method(self): pass
@classmethod
def test_class(cls): pass
@staticmethod
def test_static(): pass
"""
)
assert len(items) == 3
assert isinstance(items[0], Function)
assert items[0].name == "test_func"
assert items[0].instance is None
assert isinstance(items[1], Function)
assert items[1].name == "test_method"
assert items[1].instance is not None
assert items[1].instance.__class__.__name__ == "TestIt"
assert isinstance(items[2], Function)
assert items[2].name == "test_static"
assert items[2].instance is None

View File

@@ -24,8 +24,8 @@ from _pytest.compat import getfuncargnames
from _pytest.compat import NOTSET
from _pytest.outcomes import fail
from _pytest.pytester import Pytester
from _pytest.python import _idval
from _pytest.python import idmaker
from _pytest.python import IdMaker
from _pytest.scope import Scope
class TestMetafunc:
@@ -106,8 +106,8 @@ class TestMetafunc:
with pytest.raises(
fail.Exception,
match=(
r"In func: ids must be list of string/float/int/bool, found:"
r" Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2"
r"In func: ids contains unsupported value Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2. "
r"Supported types are: .*"
),
):
metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type]
@@ -142,16 +142,16 @@ class TestMetafunc:
@attr.s
class DummyFixtureDef:
scope = attr.ib()
_scope = attr.ib()
fixtures_defs = cast(
Dict[str, Sequence[fixtures.FixtureDef[object]]],
dict(
session_fix=[DummyFixtureDef("session")],
package_fix=[DummyFixtureDef("package")],
module_fix=[DummyFixtureDef("module")],
class_fix=[DummyFixtureDef("class")],
func_fix=[DummyFixtureDef("function")],
session_fix=[DummyFixtureDef(Scope.Session)],
package_fix=[DummyFixtureDef(Scope.Package)],
module_fix=[DummyFixtureDef(Scope.Module)],
class_fix=[DummyFixtureDef(Scope.Class)],
func_fix=[DummyFixtureDef(Scope.Function)],
),
)
@@ -160,29 +160,33 @@ class TestMetafunc:
def find_scope(argnames, indirect):
return _find_parametrized_scope(argnames, fixtures_defs, indirect=indirect)
assert find_scope(["func_fix"], indirect=True) == "function"
assert find_scope(["class_fix"], indirect=True) == "class"
assert find_scope(["module_fix"], indirect=True) == "module"
assert find_scope(["package_fix"], indirect=True) == "package"
assert find_scope(["session_fix"], indirect=True) == "session"
assert find_scope(["func_fix"], indirect=True) == Scope.Function
assert find_scope(["class_fix"], indirect=True) == Scope.Class
assert find_scope(["module_fix"], indirect=True) == Scope.Module
assert find_scope(["package_fix"], indirect=True) == Scope.Package
assert find_scope(["session_fix"], indirect=True) == Scope.Session
assert find_scope(["class_fix", "func_fix"], indirect=True) == "function"
assert find_scope(["func_fix", "session_fix"], indirect=True) == "function"
assert find_scope(["session_fix", "class_fix"], indirect=True) == "class"
assert find_scope(["package_fix", "session_fix"], indirect=True) == "package"
assert find_scope(["module_fix", "session_fix"], indirect=True) == "module"
assert find_scope(["class_fix", "func_fix"], indirect=True) == Scope.Function
assert find_scope(["func_fix", "session_fix"], indirect=True) == Scope.Function
assert find_scope(["session_fix", "class_fix"], indirect=True) == Scope.Class
assert (
find_scope(["package_fix", "session_fix"], indirect=True) == Scope.Package
)
assert find_scope(["module_fix", "session_fix"], indirect=True) == Scope.Module
# when indirect is False or is not for all scopes, always use function
assert find_scope(["session_fix", "module_fix"], indirect=False) == "function"
assert (
find_scope(["session_fix", "module_fix"], indirect=False) == Scope.Function
)
assert (
find_scope(["session_fix", "module_fix"], indirect=["module_fix"])
== "function"
== Scope.Function
)
assert (
find_scope(
["session_fix", "module_fix"], indirect=["session_fix", "module_fix"]
)
== "module"
== Scope.Module
)
def test_parametrize_and_id(self) -> None:
@@ -281,7 +285,7 @@ class TestMetafunc:
deadline=400.0
) # very close to std deadline and CI boxes are not reliable in CPU power
def test_idval_hypothesis(self, value) -> None:
escaped = _idval(value, "a", 6, None, nodeid=None, config=None)
escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6)
assert isinstance(escaped, str)
escaped.encode("ascii")
@@ -303,7 +307,10 @@ class TestMetafunc:
),
]
for val, expected in values:
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_unicode_idval_with_config(self) -> None:
"""Unit test for expected behavior to obtain ids with
@@ -331,7 +338,7 @@ class TestMetafunc:
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for val, config, expected in values:
actual = _idval(val, "a", 6, None, nodeid=None, config=config)
actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6)
assert actual == expected
def test_bytes_idval(self) -> None:
@@ -344,7 +351,10 @@ class TestMetafunc:
("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"),
]
for val, expected in values:
assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_class_or_function_idval(self) -> None:
"""Unit test for the expected behavior to obtain ids for parametrized
@@ -358,7 +368,10 @@ class TestMetafunc:
values = [(TestClass, "TestClass"), (test_function, "test_function")]
for val, expected in values:
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_notset_idval(self) -> None:
"""Test that a NOTSET value (used by an empty parameterset) generates
@@ -366,29 +379,47 @@ class TestMetafunc:
Regression test for #7686.
"""
assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0"
assert (
IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0"
)
def test_idmaker_autoname(self) -> None:
"""#250"""
result = idmaker(
("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
)
result = IdMaker(
("a", "b"),
[pytest.param("string", 1.0), pytest.param("st-ring", 2.0)],
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["string-1.0", "st-ring-2.0"]
result = idmaker(
("a", "b"), [pytest.param(object(), 1.0), pytest.param(object(), object())]
)
result = IdMaker(
("a", "b"),
[pytest.param(object(), 1.0), pytest.param(object(), object())],
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a0-1.0", "a1-b1"]
# unicode mixing, issue250
result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")])
result = IdMaker(
("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["a0-\\xc3\\xb4"]
def test_idmaker_with_bytes_regex(self) -> None:
result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
result = IdMaker(
("a"), [pytest.param(re.compile(b"foo"), 1.0)], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["foo"]
def test_idmaker_native_strings(self) -> None:
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(1.0, -1.1),
@@ -403,8 +434,14 @@ class TestMetafunc:
pytest.param(tuple("eight"), (8, -8, 8)),
pytest.param(b"\xc3\xb4", b"name"),
pytest.param(b"\xc3\xb4", "other"),
pytest.param(1.0j, -2.0j),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == [
"1.0--1.1",
"2--202",
@@ -418,10 +455,11 @@ class TestMetafunc:
"a9-b9",
"\\xc3\\xb4-name",
"\\xc3\\xb4-other",
"1j-(-0-2j)",
]
def test_idmaker_non_printable_characters(self) -> None:
result = idmaker(
result = IdMaker(
("s", "n"),
[
pytest.param("\x00", 1),
@@ -431,23 +469,35 @@ class TestMetafunc:
pytest.param("\t", 5),
pytest.param(b"\t", 6),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
def test_idmaker_manual_ids_must_be_printable(self) -> None:
result = idmaker(
result = IdMaker(
("s",),
[
pytest.param("x00", id="hello \x00"),
pytest.param("x05", id="hello \x05"),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["hello \\x00", "hello \\x05"]
def test_idmaker_enum(self) -> None:
enum = pytest.importorskip("enum")
e = enum.Enum("Foo", "one, two")
result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
result = IdMaker(
("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["Foo.one-Foo.two"]
def test_idmaker_idfn(self) -> None:
@@ -458,15 +508,19 @@ class TestMetafunc:
return repr(val)
return None
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
],
idfn=ids,
)
ids,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"]
def test_idmaker_idfn_unique_names(self) -> None:
@@ -475,15 +529,19 @@ class TestMetafunc:
def ids(val: object) -> str:
return "a"
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
],
idfn=ids,
)
ids,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a-a0", "a-a1", "a-a2"]
def test_idmaker_with_idfn_and_config(self) -> None:
@@ -513,12 +571,15 @@ class TestMetafunc:
(MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for config, expected in values:
result = idmaker(
result = IdMaker(
("a",),
[pytest.param("string")],
idfn=lambda _: "ação",
config=config,
)
lambda _: "ação",
None,
config,
None,
None,
).make_unique_parameterset_ids()
assert result == [expected]
def test_idmaker_with_ids_and_config(self) -> None:
@@ -548,12 +609,9 @@ class TestMetafunc:
(MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for config, expected in values:
result = idmaker(
("a",),
[pytest.param("string")],
ids=["ação"],
config=config,
)
result = IdMaker(
("a",), [pytest.param("string")], None, ["ação"], config, None, None
).make_unique_parameterset_ids()
assert result == [expected]
def test_parametrize_ids_exception(self, pytester: Pytester) -> None:
@@ -610,23 +668,39 @@ class TestMetafunc:
)
def test_idmaker_with_ids(self) -> None:
result = idmaker(
("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
)
result = IdMaker(
("a", "b"),
[pytest.param(1, 2), pytest.param(3, 4)],
None,
["a", None],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a", "3-4"]
def test_idmaker_with_paramset_id(self) -> None:
result = idmaker(
result = IdMaker(
("a", "b"),
[pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
ids=["a", None],
)
None,
["a", None],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["me", "you"]
def test_idmaker_with_ids_unique_names(self) -> None:
result = idmaker(
("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
)
result = IdMaker(
("a"),
list(map(pytest.param, [1, 2, 3, 4, 5])),
None,
["a", "a", "b", "c", "b"],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a0", "a1", "b0", "c", "b1"]
def test_parametrize_indirect(self) -> None:
@@ -692,9 +766,8 @@ class TestMetafunc:
"""
#714
Test parametrization with 'indirect' parameter applied on
particular arguments. As y is is direct, its value should
be used directly rather than being passed to the fixture
y.
particular arguments. As y is direct, its value should
be used directly rather than being passed to the fixture y.
:param pytester: the instance of Pytester class, a temporary
test directory.
@@ -1266,7 +1339,7 @@ class TestMetafuncFunctional:
"""
import pytest
@pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type))
@pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, OSError()))
def test_ids_numbers(x,expected):
assert x * 2 == expected
"""
@@ -1274,8 +1347,8 @@ class TestMetafuncFunctional:
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"In test_ids_numbers: ids must be list of string/float/int/bool,"
" found: <class 'type'> (type: <class 'type'>) at index 2"
"In test_ids_numbers: ids contains unsupported value OSError() (type: <class 'OSError'>) at index 2. "
"Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
]
)

View File

@@ -19,6 +19,16 @@ class TestRaises:
excinfo = pytest.raises(ValueError, int, "hello")
assert "invalid literal" in str(excinfo.value)
def test_raises_does_not_allow_none(self):
with pytest.raises(ValueError, match="Expected an exception type or"):
# We're testing that this invalid usage gives a helpful error,
# so we can ignore Mypy telling us that None is invalid.
pytest.raises(expected_exception=None) # type: ignore
def test_raises_does_not_allow_empty_tuple(self):
with pytest.raises(ValueError, match="Expected an exception type or"):
pytest.raises(expected_exception=())
def test_raises_callable_no_exception(self) -> None:
class A:
def __call__(self):
@@ -82,13 +92,9 @@ class TestRaises:
def test_does_not_raise(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
from contextlib import contextmanager
from contextlib import nullcontext as does_not_raise
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize('example_input,expectation', [
(3, does_not_raise()),
(2, does_not_raise()),
@@ -107,13 +113,9 @@ class TestRaises:
def test_does_not_raise_does_raise(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
from contextlib import contextmanager
from contextlib import nullcontext as does_not_raise
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize('example_input,expectation', [
(0, does_not_raise()),
(1, pytest.raises(ZeroDivisionError)),
@@ -144,7 +146,7 @@ class TestRaises:
try:
pytest.raises(ValueError, int, "0")
except pytest.fail.Exception as e:
assert e.msg == "DID NOT RAISE {}".format(repr(ValueError))
assert e.msg == f"DID NOT RAISE {repr(ValueError)}"
else:
assert False, "Expected pytest.raises.Exception"
@@ -152,7 +154,7 @@ class TestRaises:
with pytest.raises(ValueError):
pass
except pytest.fail.Exception as e:
assert e.msg == "DID NOT RAISE {}".format(repr(ValueError))
assert e.msg == f"DID NOT RAISE {repr(ValueError)}"
else:
assert False, "Expected pytest.raises.Exception"
@@ -191,10 +193,12 @@ class TestRaises:
int("asdf")
msg = "with base 16"
expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format(
msg
expr = (
"Regex pattern did not match.\n"
f" Regex: {msg!r}\n"
" Input: \"invalid literal for int() with base 10: 'asdf'\""
)
with pytest.raises(AssertionError, match=re.escape(expr)):
with pytest.raises(AssertionError, match="(?m)" + re.escape(expr)):
with pytest.raises(ValueError, match=msg):
int("asdf", base=10)
@@ -217,7 +221,7 @@ class TestRaises:
with pytest.raises(AssertionError, match="'foo"):
raise AssertionError("'bar")
(msg,) = excinfo.value.args
assert msg == 'Regex pattern "\'foo" does not match "\'bar".'
assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"'''
def test_match_failure_exact_string_message(self):
message = "Oh here is a message with (42) numbers in parameters"
@@ -226,9 +230,10 @@ class TestRaises:
raise AssertionError(message)
(msg,) = excinfo.value.args
assert msg == (
"Regex pattern 'Oh here is a message with (42) numbers in "
"parameters' does not match 'Oh here is a message with (42) "
"numbers in parameters'. Did you mean to `re.escape()` the regex?"
"Regex pattern did not match.\n"
" Regex: 'Oh here is a message with (42) numbers in parameters'\n"
" Input: 'Oh here is a message with (42) numbers in parameters'\n"
" Did you mean to `re.escape()` the regex?"
)
def test_raises_match_wrong_type(self):

View File

@@ -29,7 +29,7 @@ def test_fixtures_in_module(pytester: Pytester) -> None:
[
"*fixtures used by test_arg1*",
"*(test_fixtures_in_module.py:9)*",
"arg1",
"arg1 -- test_fixtures_in_module.py:6",
" arg1 docstring",
]
)
@@ -68,17 +68,16 @@ def test_fixtures_in_conftest(pytester: Pytester) -> None:
[
"*fixtures used by test_arg2*",
"*(test_fixtures_in_conftest.py:2)*",
"arg2",
"arg2 -- conftest.py:6",
" arg2 docstring",
"*fixtures used by test_arg3*",
"*(test_fixtures_in_conftest.py:4)*",
"arg1",
"arg1 -- conftest.py:3",
" arg1 docstring",
"arg2",
"arg2 -- conftest.py:6",
" arg2 docstring",
"arg3",
"arg3 -- conftest.py:9",
" arg3",
" docstring",
]
)
@@ -112,9 +111,9 @@ def test_should_show_fixtures_used_by_test(pytester: Pytester) -> None:
[
"*fixtures used by test_args*",
"*(test_should_show_fixtures_used_by_test.py:6)*",
"arg1",
"arg1 -- test_should_show_fixtures_used_by_test.py:3",
" arg1 from testmodule",
"arg2",
"arg2 -- conftest.py:6",
" arg2 from conftest",
]
)
@@ -181,3 +180,75 @@ def test_doctest_items(pytester: Pytester) -> None:
assert result.ret == 0
result.stdout.fnmatch_lines(["*collected 2 items*"])
def test_multiline_docstring_in_module(pytester: Pytester) -> None:
p = pytester.makepyfile(
'''
import pytest
@pytest.fixture
def arg1():
"""Docstring content that spans across multiple lines,
through second line,
and through third line.
Docstring content that extends into a second paragraph.
Docstring content that extends into a third paragraph.
"""
def test_arg1(arg1):
pass
'''
)
result = pytester.runpytest("--fixtures-per-test", p)
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*fixtures used by test_arg1*",
"*(test_multiline_docstring_in_module.py:13)*",
"arg1 -- test_multiline_docstring_in_module.py:3",
" Docstring content that spans across multiple lines,",
" through second line,",
" and through third line.",
]
)
def test_verbose_include_multiline_docstring(pytester: Pytester) -> None:
p = pytester.makepyfile(
'''
import pytest
@pytest.fixture
def arg1():
"""Docstring content that spans across multiple lines,
through second line,
and through third line.
Docstring content that extends into a second paragraph.
Docstring content that extends into a third paragraph.
"""
def test_arg1(arg1):
pass
'''
)
result = pytester.runpytest("--fixtures-per-test", "-v", p)
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*fixtures used by test_arg1*",
"*(test_verbose_include_multiline_docstring.py:13)*",
"arg1 -- test_verbose_include_multiline_docstring.py:3",
" Docstring content that spans across multiple lines,",
" through second line,",
" and through third line.",
" ",
" Docstring content that extends into a second paragraph.",
" ",
" Docstring content that extends into a third paragraph.",
]
)