diff --git a/LICENSE b/LICENSE index 19206cd9..dc9c045a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ BSD 3-Clause License -Copyright (c) 2018-2021, Sylvain MariƩ, Schneider Electric Industries +Copyright (c) 2018-2025, Sylvain MariƩ, Schneider Electric Industries All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/docs/changelog.md b/docs/changelog.md index 519a2e48..04e70446 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,10 +1,16 @@ # Changelog -### 3.8.7 - compatibility with pytest 8.6.0 +### 3.9.0 - support for python 3.14 and pytest 8.6 +- Fixed `AttributeError: 'MiniMetafunc' object has no attribute '_params_directness'` when a case function is + parametrized or requires a fixtures, with pytest 8.4.0. Fixed + [#365](https://github.com/smarie/python-pytest-cases/issues/365) +- Fixed `ValueError: The provided fixture function does not seem to be a fixture` with `@fixture_ref` with pytest + 8.4.0. Fixed [#364](https://github.com/smarie/python-pytest-cases/issues/364) - Dropped support for `python<3.9` and `pytest<6`. Fixes [#362](https://github.com/smarie/python-pytest-cases/issues/362) and fixes [#186](https://github.com/smarie/python-pytest-cases/issues/186) +- Fixed test suite for python 3.14, officializing the support for this version. ### 3.8.6 - compatibility fix diff --git a/noxfile.py b/noxfile.py index c4b63b44..410dd696 100644 --- a/noxfile.py +++ b/noxfile.py @@ -51,17 +51,26 @@ class Folders: ENVS = { - # python 3.14 - numpy is not available in precompiled version for this python version yet - # (PY314, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, + # python 3.14 + (PY314, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, + (PY314, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, + (PY314, "pytest6.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<7"}}, # python 3.13 (PY313, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, + (PY313, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, + (PY313, "pytest6.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<7"}}, # python 3.12 (PY312, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, (PY312, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, + (PY312, "pytest6.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<7"}}, # python 3.11 - # We'll run this last for coverage + # We'll run 'pytest-latest' this last for coverage + (PY311, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, + (PY311, "pytest6.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<7"}}, # python 3.10 (PY310, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, + (PY310, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, + (PY310, "pytest6.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<7"}}, # python 3.9 (PY39, "pytest-latest"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": ""}}, (PY39, "pytest7.x"): {"coverage": False, "pkg_specs": {"pip": ">19", "pytest": "<8"}}, diff --git a/src/pytest_cases/case_parametrizer_new.py b/src/pytest_cases/case_parametrizer_new.py index 9796f062..ddd36bb8 100644 --- a/src/pytest_cases/case_parametrizer_new.py +++ b/src/pytest_cases/case_parametrizer_new.py @@ -421,8 +421,9 @@ def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType] If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref` will be returned. - If `case_fun` is a parametrized case, one `lazy_value` with a partialized version will be created for each parameter - combination. + If `case_fun` is a parametrized case, (NEW since 3.0.0) a fixture will be created if not yet present, + and a `fixture_ref` will be returned. (OLD < 3.0.0) one `lazy_value` with a partialized version will be created + for each parameter combination. Otherwise, `case_fun` represents a single case: in that case a single `lazy_value` is returned. @@ -734,12 +735,27 @@ def extract_cases_from_class(cls, ): # type: (...) -> List[Callable] """ + Collects all case functions (methods matching ``case_fun_prefix``) in class ``cls``. - :param cls: - :param check_name: - :param case_fun_prefix: - :param _case_param_factory: - :return: + Parameters + ---------- + cls : Type + A class where to look for case functions. All methods matching ``prefix`` will be returned. + + check_name : bool + If this is ``True`` and class name does not contain the string ``Case``, the class will not be inspected and + an empty list will be returned. + + case_fun_prefix : str + A prefix that case functions (class methods) must match to be collected. + + _case_param_factory : + Legacy. Not used. + + Returns + ------- + cases_lst : List[Callable] + A list of collected case functions (class methods). """ if is_case_class(cls, check_name=check_name): # see from _pytest.python import pytest_pycollect_makeitem @@ -769,7 +785,7 @@ def extract_cases_from_class(cls, return [] -def extract_cases_from_module(module, # type: ModuleRef +def extract_cases_from_module(module, # type: Union[str, ModuleRef] package_name=None, # type: str case_fun_prefix=CASE_PREFIX_FUN, # type: str _case_param_factory=None @@ -782,10 +798,27 @@ def extract_cases_from_module(module, # type: ModuleRe See also `_pytest.python.PyCollector.collect` and `_pytest.python.PyCollector._makeitem` and `_pytest.python.pytest_pycollect_makeitem`: we could probably do this in a better way in pytest_pycollect_makeitem - :param module: - :param package_name: - :param _case_param_factory: - :return: + Parameters + ---------- + module : Union[str, ModuleRef] + A module where to look for case functions. All functions in the module matching ``prefix`` will be + returned. In addition, all classes in the module with ``Case`` in their name will be inspected. For each of + them, all methods matching ``prefix`` will be returned too. + + package_name : Optional[str], default: None + If ``module`` is provided as a string, this is a mandatory package full qualified name (e.g. ``a.b.c``) where + to import the module from. + + case_fun_prefix : str + A prefix that case functions (including class methods) must match to be collected. + + _case_param_factory : + Legacy. Not used. + + Returns + ------- + cases : List[Callable] + A list of case functions """ # optionally import module if passed as module name string if isinstance(module, string_types): @@ -805,12 +838,30 @@ def _extract_cases_from_module_or_class(module=None, # type cls=None, # type: Type case_fun_prefix=CASE_PREFIX_FUN, # type: str _case_param_factory=None - ): + ): # type: (...) -> List[Callable] """ + Extracts all case functions from `module` or `cls` (only one non-None must be provided). - :param module: - :param _case_param_factory: - :return: + Parameters + ---------- + module : Optional[ModuleRef], default: None + A module where to look for case functions. All functions in the module matching ``prefix`` will be + returned. In addition, all classes in the module with ``Case`` in their name will be inspected. For each of + them, all methods matching ``prefix`` will be returned too. + + cls : Optional[Type], default: None + A class where to look for case functions. All methods matching ``prefix`` will be returned. + + case_fun_prefix : str + A prefix that case functions (including class methods) must match to be collected. + + _case_param_factory : + Legacy. Not used. + + Returns + ------- + cases : List[Callable] + A list of case functions """ if not ((cls is None) ^ (module is None)): raise ValueError("Only one of cls or module should be provided") @@ -897,6 +948,7 @@ def _of_interest(x): # noqa % (m, cases_dct[co_firstlineno])) cases_dct[co_firstlineno] = m else: + # Not used anymore # Legacy usage where the cases generators were expanded here and inserted with a virtual line no _case_param_factory(m, co_firstlineno, cases_dct) diff --git a/src/pytest_cases/common_pytest.py b/src/pytest_cases/common_pytest.py index 99c6014e..afad9d39 100644 --- a/src/pytest_cases/common_pytest.py +++ b/src/pytest_cases/common_pytest.py @@ -31,7 +31,7 @@ from .common_pytest_marks import make_marked_parameter_value, get_param_argnames_as_list, \ get_pytest_parametrize_marks, get_pytest_usefixture_marks, PYTEST3_OR_GREATER, PYTEST6_OR_GREATER, \ PYTEST38_OR_GREATER, PYTEST34_OR_GREATER, PYTEST33_OR_GREATER, PYTEST32_OR_GREATER, PYTEST71_OR_GREATER, \ - PYTEST8_OR_GREATER + PYTEST8_OR_GREATER, PYTEST84_OR_GREATER from .common_pytest_lazy_values import is_lazy_value, is_lazy @@ -85,45 +85,81 @@ def remove_duplicates(lst): if item not in dset and not dset.add(item)] -def is_fixture(fixture_fun # type: Any - ): - """ - Returns True if the provided function is a fixture +if PYTEST84_OR_GREATER: + def is_fixture(fixture_fun # type: Any + ): + """ + Returns True if the provided function is a fixture - :param fixture_fun: - :return: - """ - try: - fixture_fun._pytestfixturefunction # noqa - return True - except AttributeError: - # not a fixture ? - return False + :param fixture_fun: + :return: + """ + from _pytest.fixtures import FixtureFunctionDefinition + return safe_isinstance(fixture_fun, FixtureFunctionDefinition) +else: + def is_fixture(fixture_fun # type: Any + ): + """ + Returns True if the provided function is a fixture + + :param fixture_fun: + :return: + """ + try: + fixture_fun._pytestfixturefunction # noqa + return True + except AttributeError: + # not a fixture ? + return False -def list_all_fixtures_in(cls_or_module, return_names=True, recurse_to_module=False): - """ - Returns a list containing all fixture names (or symbols if `return_names=False`) - in the given class or module. +if PYTEST84_OR_GREATER: + def list_all_fixtures_in(cls_or_module, return_names=True, recurse_to_module=False): + """ + Returns a list containing all fixture names (or symbols if `return_names=False`) + in the given class or module. - Note that `recurse_to_module` can be used so that the fixtures in the parent - module of a class are listed too. + Note that `recurse_to_module` can be used so that the fixtures in the parent + module of a class are listed too. - :param cls_or_module: - :param return_names: - :param recurse_to_module: - :return: - """ - res = [get_fixture_name(symb) if return_names else symb - for n, symb in inspect.getmembers(cls_or_module, lambda f: inspect.isfunction(f) or inspect.ismethod(f)) - if is_fixture(symb)] + :param cls_or_module: + :param return_names: + :param recurse_to_module: + :return: + """ + res = [get_fixture_name(symb) if return_names else symb + for n, symb in inspect.getmembers(cls_or_module, is_fixture)] - if recurse_to_module and not inspect.ismodule(cls_or_module): - # TODO currently this only works for a single level of nesting, we should use __qualname__ (py3) or .im_class - host = import_module(cls_or_module.__module__) - res += list_all_fixtures_in(host, recurse_to_module=True, return_names=return_names) + if recurse_to_module and not inspect.ismodule(cls_or_module): + # TODO currently this only works for a single level of nesting, we should use __qualname__ (py3) or .im_class + host = import_module(cls_or_module.__module__) + res += list_all_fixtures_in(host, recurse_to_module=True, return_names=return_names) - return res + return res +else: + def list_all_fixtures_in(cls_or_module, return_names=True, recurse_to_module=False): + """ + Returns a list containing all fixture names (or symbols if `return_names=False`) + in the given class or module. + + Note that `recurse_to_module` can be used so that the fixtures in the parent + module of a class are listed too. + + :param cls_or_module: + :param return_names: + :param recurse_to_module: + :return: + """ + res = [get_fixture_name(symb) if return_names else symb + for n, symb in inspect.getmembers(cls_or_module, lambda f: inspect.isfunction(f) or inspect.ismethod(f)) + if is_fixture(symb)] + + if recurse_to_module and not inspect.ismodule(cls_or_module): + # TODO currently this only works for a single level of nesting, we should use __qualname__ (py3) or .im_class + host = import_module(cls_or_module.__module__) + res += list_all_fixtures_in(host, recurse_to_module=True, return_names=return_names) + + return res def safe_isclass(obj # type: object @@ -159,54 +195,93 @@ def assert_is_fixture(fixture_fun # type: Any "it ?" % fixture_fun) -def get_fixture_name(fixture_fun # type: Union[str, Callable] - ): - """ - Internal utility to retrieve the fixture name corresponding to the given fixture function. - Indeed there is currently no pytest API to do this. +if PYTEST84_OR_GREATER: + def get_fixture_name(fixture_fun # type: Union[str, Callable] + ): + """ + Internal utility to retrieve the fixture name corresponding to the given fixture function. + Indeed there is currently no pytest API to do this. - Note: this function can receive a string, in which case it is directly returned. + Note: this function can receive a string, in which case it is directly returned. - :param fixture_fun: - :return: - """ - if isinstance(fixture_fun, string_types): - return fixture_fun - assert_is_fixture(fixture_fun) - try: # pytest 3 - custom_fixture_name = fixture_fun._pytestfixturefunction.name # noqa - except AttributeError: - try: # pytest 2 - custom_fixture_name = fixture_fun.func_name # noqa - except AttributeError: - custom_fixture_name = None + :param fixture_fun: + :return: + """ + if isinstance(fixture_fun, string_types): + return fixture_fun - if custom_fixture_name is not None: - # there is a custom fixture name - return custom_fixture_name - else: - obj__name = getattr(fixture_fun, '__name__', None) - if obj__name is not None: - # a function, probably - return obj__name + assert_is_fixture(fixture_fun) + + if fixture_fun.name is None: + # As opposed to pytest < 8.4.0, the merge between custom name and function name has already been made, + # this should not happen. + # See https://github.com/nicoddemus/pytest/commit/ecde993e17efb3f34157642a111ba20f476aa80a + raise NotImplementedError + + return fixture_fun.name + +else: + def get_fixture_name(fixture_fun # type: Union[str, Callable] + ): + """ + Internal utility to retrieve the fixture name corresponding to the given fixture function. + Indeed there is currently no pytest API to do this. + + Note: this function can receive a string, in which case it is directly returned. + + :param fixture_fun: + :return: + """ + if isinstance(fixture_fun, string_types): + return fixture_fun + assert_is_fixture(fixture_fun) + try: # pytest 3 + custom_fixture_name = fixture_fun._pytestfixturefunction.name # noqa + except AttributeError: + try: # pytest 2 + custom_fixture_name = fixture_fun.func_name # noqa + except AttributeError: + custom_fixture_name = None + + if custom_fixture_name is not None: + # there is a custom fixture name + return custom_fixture_name else: - # a callable object probably - return str(fixture_fun) + obj__name = getattr(fixture_fun, '__name__', None) + if obj__name is not None: + # a function, probably + return obj__name + else: + # a callable object probably + return str(fixture_fun) -def get_fixture_scope(fixture_fun): - """ - Internal utility to retrieve the fixture scope corresponding to the given fixture function . - Indeed there is currently no pytest API to do this. +if PYTEST84_OR_GREATER: + def get_fixture_scope(fixture_fun): + """ + Internal utility to retrieve the fixture scope corresponding to the given fixture function . + Indeed there is currently no pytest API to do this. - :param fixture_fun: - :return: - """ - assert_is_fixture(fixture_fun) - return fixture_fun._pytestfixturefunction.scope # noqa - # except AttributeError: - # # pytest 2 - # return fixture_fun.func_scope + :param fixture_fun: + :return: + """ + assert_is_fixture(fixture_fun) + # See https://github.com/nicoddemus/pytest/commit/ecde993e17efb3f34157642a111ba20f476aa80a + return fixture_fun._fixture_function_marker.scope # noqa +else: + def get_fixture_scope(fixture_fun): + """ + Internal utility to retrieve the fixture scope corresponding to the given fixture function . + Indeed there is currently no pytest API to do this. + + :param fixture_fun: + :return: + """ + assert_is_fixture(fixture_fun) + return fixture_fun._pytestfixturefunction.scope # noqa + # except AttributeError: + # # pytest 2 + # return fixture_fun.func_scope # ---------------- working on pytest nodes (e.g. Function) @@ -681,7 +756,14 @@ def __init__(self, nodeid): class MiniMetafunc(Metafunc): """ A class to know what pytest *would* do for a given function in terms of callspec. - It is used in function `case_to_argvalues` + It is ONLY used in function `case_to_argvalues` and only the following are read: + + - is_parametrized (bool) + - requires_fixtures (bool) + - fixturenames_not_in_sig (declared used fixtures with @pytest.mark.usefixtures) + + Computation of the latter requires + """ # noinspection PyMissingConstructor def __init__(self, func): @@ -701,6 +783,7 @@ def __init__(self, func): self.function = func self.definition = MiniFuncDef(func.__name__) self._calls = [] + self._params_directness = {} # non-default parameters self.fixturenames = getfuncargnames(func) # add declared used fixtures with @pytest.mark.usefixtures diff --git a/src/pytest_cases/common_pytest_marks.py b/src/pytest_cases/common_pytest_marks.py index a017922c..74ce1b5b 100644 --- a/src/pytest_cases/common_pytest_marks.py +++ b/src/pytest_cases/common_pytest_marks.py @@ -45,7 +45,7 @@ PYTEST71_OR_GREATER = PYTEST_VERSION >= Version('7.1.0') PYTEST8_OR_GREATER = PYTEST_VERSION >= Version('8.0.0') PYTEST81_OR_GREATER = PYTEST_VERSION >= Version('8.1.0') -PYTEST811 = PYTEST_VERSION == Version('8.1.1') +PYTEST84_OR_GREATER = PYTEST_VERSION >= Version('8.4.0') def get_param_argnames_as_list(argnames): diff --git a/tests/cases/doc/test_doc_filters_n_tags2.py b/tests/cases/doc/test_doc_filters_n_tags2.py index 14f49a1f..ca919dce 100644 --- a/tests/cases/doc/test_doc_filters_n_tags2.py +++ b/tests/cases/doc/test_doc_filters_n_tags2.py @@ -2,6 +2,8 @@ # + All contributors to # # License: 3-clause BSD, +import sys + from math import sqrt import pytest @@ -9,13 +11,16 @@ from pytest_cases import parametrize_with_cases, get_case_id +IS_PY314 = sys.version_info[0:2] >= (3, 14) + + def case_int_success(): return 1 def case_negative_int_failure(): # note that we decide to return the expected type of failure to check it - return -1, ValueError, "math domain error" + return -1, ValueError, "expected a nonnegative input, got -1.0" if IS_PY314 else "math domain error" @parametrize_with_cases("data", cases='.', glob="*success") diff --git a/tests/cases/issues/test_issue_126_2.py b/tests/cases/issues/test_issue_126_2.py index 6b21c5cd..6e694af5 100644 --- a/tests/cases/issues/test_issue_126_2.py +++ b/tests/cases/issues/test_issue_126_2.py @@ -2,6 +2,11 @@ # + All contributors to # # License: 3-clause BSD, +""" +This test file is about chacking that all fixture naming conflicts are handled correctly, between fixtures in the +module, in the test classes, and the fixtures generated for the case functions when they are parametrized or require +fixtures. +""" import pytest from pytest_cases.common_pytest_marks import has_pytest_param @@ -12,42 +17,69 @@ if has_pytest_param: @pytest.fixture def b(): + """This fixture has the same name as the fixtures created for cases in the test_issue_126_2_cases.py""" return -1 @pytest.fixture(name='a') def a_in_module(): + """This fixture has the same name as the fixtures created for cases in the test_issue_126_2_cases.py""" return 1 class TestA: @pytest.fixture(name='a') def a_nested(self): + """This fixture has the same name as the fixtures created for cases in the test_issue_126_2_cases.py""" return 2 def test_a_nested(self, a): + """Here the requested fixture is `a` so it is the one from this file, not the one generated from the case + file. Since the nested one overrides the module one, it is 2 and not 1""" assert a == 2 @parametrize_with_cases('o', debug=True) def test_foo_nested(self, o): + """ + Here parameter o will receive as argvalues the various cases defined in the test_issue_126_2_cases.py, + all equal to 'case!'. If it receives "1" or "-1", it means that the fixtures generated for the cases did + not well manage to coexists with the fixtures in this file, above. + """ assert o == 'case!' @parametrize_with_cases('o', debug=True) def test_foo_nested2(self, o): + """ + Here parameter o will receive as argvalues the various cases defined in the test_issue_126_2_cases.py, + all equal to 'case!'. If it receives "1" or "-1", it means that the fixtures generated for the cases did + not well manage to coexists with the fixtures in this file, above. + """ assert o == 'case!' def test_bar(a): + """Here the requested fixture is `a` so it is the one from this file, not the one generated from the case + file. S it is 1""" assert a == 1 @parametrize_with_cases('o', debug=True) def test_foo(o): + """ + Here parameter o will receive as argvalues the various cases defined in the test_issue_126_2_cases.py, + all equal to 'case!'. If it receives "1" or "-1", it means that the fixtures generated for the cases did + not well manage to coexists with the fixtures in this file, above. + """ assert o == 'case!' @parametrize_with_cases('o', debug=True) def test_foo2(o): + """ + Here parameter o will receive as argvalues the various cases defined in the test_issue_126_2_cases.py, + all equal to 'case!'. If it receives "1" or "-1", it means that the fixtures generated for the cases did + not well manage to coexists with the fixtures in this file, above. + """ assert o == 'case!' def test_synthesis(module_results_dct): diff --git a/tests/cases/issues/test_issue_286.py b/tests/cases/issues/test_issue_286.py index 2ebb6b37..3a713274 100644 --- a/tests/cases/issues/test_issue_286.py +++ b/tests/cases/issues/test_issue_286.py @@ -1,13 +1,21 @@ -import pytest from inspect import isgeneratorfunction from pytest_cases import fixture, parametrize +from pytest_cases.common_pytest_marks import PYTEST84_OR_GREATER + def test_isgeneratorfunction_fixture(): @fixture def dummy(): yield - assert isgeneratorfunction(dummy.__pytest_wrapped__.obj) + if PYTEST84_OR_GREATER: + # See https://github.com/pytest-dev/pytest/pull/12473 + import inspect + obj = inspect.unwrap(dummy) + else: + obj = dummy.__pytest_wrapped__.obj + + assert isgeneratorfunction(obj) # this covers parametrize aswell as parametrize_with_cases diff --git a/tests/cases/issues/test_py35_issue_286.py b/tests/cases/issues/test_py35_issue_286.py index 0bca57bb..59cd6a59 100644 --- a/tests/cases/issues/test_py35_issue_286.py +++ b/tests/cases/issues/test_py35_issue_286.py @@ -1,13 +1,21 @@ -import pytest from inspect import iscoroutinefunction from pytest_cases import fixture, parametrize +from pytest_cases.common_pytest_marks import PYTEST84_OR_GREATER + def test_iscoroutinefunction_fixture(): @fixture async def dummy(): return - assert iscoroutinefunction(dummy.__pytest_wrapped__.obj) + if PYTEST84_OR_GREATER: + # See https://github.com/pytest-dev/pytest/pull/12473 + import inspect + obj = inspect.unwrap(dummy) + else: + obj = dummy.__pytest_wrapped__.obj + + assert iscoroutinefunction(obj) # this covers parametrize aswell as parametrize_with_cases diff --git a/tests/cases/issues/test_py36_issue_286.py b/tests/cases/issues/test_py36_issue_286.py index 2b35a90b..08227443 100644 --- a/tests/cases/issues/test_py36_issue_286.py +++ b/tests/cases/issues/test_py36_issue_286.py @@ -1,13 +1,21 @@ -import pytest from inspect import isasyncgenfunction from pytest_cases import fixture, parametrize +from pytest_cases.common_pytest_marks import PYTEST84_OR_GREATER + def test_isasyncgenfunction_fixture(): @fixture async def dummy(): yield - assert isasyncgenfunction(dummy.__pytest_wrapped__.obj) + if PYTEST84_OR_GREATER: + # See https://github.com/pytest-dev/pytest/pull/12473 + import inspect + obj = inspect.unwrap(dummy) + else: + obj = dummy.__pytest_wrapped__.obj + + assert isasyncgenfunction(obj) # this covers parametrize aswell as parametrize_with_cases diff --git a/tests/pytest_extension/order/session_optim/test_reorder_default_normal.py b/tests/pytest_extension/order/session_optim/test_reorder_default_normal.py index 64373833..26fb01a4 100644 --- a/tests/pytest_extension/order/session_optim/test_reorder_default_normal.py +++ b/tests/pytest_extension/order/session_optim/test_reorder_default_normal.py @@ -4,7 +4,7 @@ # License: 3-clause BSD, import pytest -from pytest_cases.common_pytest_marks import PYTEST3_OR_GREATER, PYTEST361_36X, PYTEST811, PYTEST81_OR_GREATER +from pytest_cases.common_pytest_marks import PYTEST3_OR_GREATER, PYTEST361_36X, PYTEST81_OR_GREATER def test_config(request): diff --git a/tests/test_common_pytest.py b/tests/test_common_pytest.py new file mode 100644 index 00000000..40bb76f9 --- /dev/null +++ b/tests/test_common_pytest.py @@ -0,0 +1,59 @@ +# Authors: Sylvain MARIE +# + All contributors to +# +# License: 3-clause BSD, +import sys + +import pytest +from pytest_cases.common_pytest import list_all_fixtures_in, is_fixture, get_fixture_name, get_fixture_scope + +@pytest.fixture +def great_fixture_123987(): + return "hey" + + +@pytest.fixture(scope='session', name="hey_123988") +def great_fixture_123988(): + return "hey2" + + +THIS_MODULE = sys.modules[__name__] + + +def test_is_fixture(): + assert is_fixture(great_fixture_123987) + assert is_fixture(great_fixture_123988) + + +def test_get_fixture_name(): + assert get_fixture_name(great_fixture_123987) == "great_fixture_123987" + # Overridden name + assert get_fixture_name(great_fixture_123988) == "hey_123988" + + +def test_get_fixture_scope(): + assert get_fixture_scope(great_fixture_123987) == "function" + # Overridden scope + assert get_fixture_scope(great_fixture_123988) == "session" + + +class Foo: + def foo(self): + return + + +@pytest.mark.parametrize("not_a_fixture", (False, 1, test_is_fixture, Foo, Foo.foo)) +def test_is_fixture_negative(not_a_fixture): + assert not is_fixture(not_a_fixture) + + +def test_list_all_fixtures_in__return_name(): + all_fixtures = list_all_fixtures_in(THIS_MODULE, return_names=True) + assert "great_fixture_123987" in all_fixtures + assert "hey_123988" in all_fixtures + + +def test_list_all_fixtures_in__return_symbols(): + all_fixtures = list_all_fixtures_in(THIS_MODULE, return_names=False) + assert great_fixture_123987 in all_fixtures + assert great_fixture_123988 in all_fixtures