Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pytest8 #335

Merged
merged 3 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/pytest_cases/common_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
from .common_others import get_function_host
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
PYTEST38_OR_GREATER, PYTEST34_OR_GREATER, PYTEST33_OR_GREATER, PYTEST32_OR_GREATER, PYTEST71_OR_GREATER, \
PYTEST8_OR_GREATER
from .common_pytest_lazy_values import is_lazy_value, is_lazy


Expand Down Expand Up @@ -653,14 +654,27 @@ def getfuncargnames(function, cls=None):
return arg_names


class FakeSession(object):
__slots__ = ('_fixturemanager',)

def __init__(self):
self._fixturemanager = None


class MiniFuncDef(object):
__slots__ = ('nodeid',)
__slots__ = ('nodeid', 'session')

def __init__(self, nodeid):
self.nodeid = nodeid
if PYTEST8_OR_GREATER:
self.session = FakeSession()


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`
"""
# noinspection PyMissingConstructor
def __init__(self, func):
from .plugin import PYTEST_CONFIG # late import to ensure config has been loaded by now
Expand All @@ -685,12 +699,18 @@ def __init__(self, func):
self.fixturenames_not_in_sig = [f for f in get_pytest_usefixture_marks(func) if f not in self.fixturenames]
if self.fixturenames_not_in_sig:
self.fixturenames = tuple(self.fixturenames_not_in_sig + list(self.fixturenames))

if PYTEST8_OR_GREATER:
# dummy
self._arg2fixturedefs = dict() # type: dict[str, Sequence["FixtureDef[Any]"]]

# get parametrization marks
self.pmarks = get_pytest_parametrize_marks(self.function)
if self.is_parametrized:
self.update_callspecs()
# preserve order
self.required_fixtures = tuple(f for f in self.fixturenames if f not in self._calls[0].funcargs)
ref_names = self._calls[0].params if PYTEST8_OR_GREATER else self._calls[0].funcargs
self.required_fixtures = tuple(f for f in self.fixturenames if f not in ref_names)
else:
self.required_fixtures = self.fixturenames

Expand Down Expand Up @@ -773,8 +793,7 @@ def get_callspecs(func):
Returns a list of pytest CallSpec objects corresponding to calls that should be made for this parametrized function.
This mini-helper assumes no complex things (scope='function', indirect=False, no fixtures, no custom configuration)

:param func:
:return:
Note that this function is currently only used in tests.
"""
meta = MiniMetafunc(func)
# meta.update_callspecs()
Expand Down
1 change: 1 addition & 0 deletions src/pytest_cases/common_pytest_marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
PYTEST6_OR_GREATER = PYTEST_VERSION >= Version('6.0.0')
PYTEST7_OR_GREATER = PYTEST_VERSION >= Version('7.0.0')
PYTEST71_OR_GREATER = PYTEST_VERSION >= Version('7.1.0')
PYTEST8_OR_GREATER = PYTEST_VERSION >= Version('8.0.0')


def get_param_argnames_as_list(argnames):
Expand Down
85 changes: 55 additions & 30 deletions src/pytest_cases/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

from .common_mini_six import string_types
from .common_pytest_lazy_values import get_lazy_args
from .common_pytest_marks import PYTEST35_OR_GREATER, PYTEST46_OR_GREATER, PYTEST37_OR_GREATER, PYTEST7_OR_GREATER
from .common_pytest_marks import PYTEST35_OR_GREATER, PYTEST46_OR_GREATER, PYTEST37_OR_GREATER, PYTEST7_OR_GREATER, PYTEST8_OR_GREATER
from .common_pytest import get_pytest_nodeid, get_pytest_function_scopeval, is_function_node, get_param_names, \
get_param_argnames_as_list, has_function_scope, set_callspec_arg_scope_to_function

Expand All @@ -41,7 +41,8 @@
from .case_parametrizer_new import get_current_cases


_DEBUG = False
_DEBUG = True
"""Note: this is a manual flag to turn when developing (do not forget to also call pytest with -s)"""


# @pytest.hookimpl(hookwrapper=True, tryfirst=True)
Expand Down Expand Up @@ -753,38 +754,56 @@ def remove_all(self, values):
self._update_fixture_defs()


def getfixtureclosure(fm, fixturenames, parentnode, ignore_args=()):
"""
Replaces pytest's getfixtureclosure method to handle unions.
"""
if PYTEST8_OR_GREATER:
def getfixtureclosure(fm, parentnode, initialnames, ignore_args):
"""
Replaces pytest's getfixtureclosure method to handle unions.
"""
# (1) first retrieve the normal pytest output for comparison
ref_fixturenames, ref_arg2fixturedefs = fm.__class__.getfixtureclosure(fm, parentnode, initialnames, ignore_args)

# (1) first retrieve the normal pytest output for comparison
kwargs = dict()
if PYTEST46_OR_GREATER:
# new argument "ignore_args" in 4.6+
kwargs['ignore_args'] = ignore_args
# (2) now let's do it by ourselves to support fixture unions
_init_fixnames, super_closure, arg2fixturedefs = create_super_closure(fm, parentnode, ref_fixturenames, ignore_args)

if PYTEST37_OR_GREATER:
# three outputs
initial_names, ref_fixturenames, ref_arg2fixturedefs = \
fm.__class__.getfixtureclosure(fm, fixturenames, parentnode, **kwargs)
else:
# two outputs
ref_fixturenames, ref_arg2fixturedefs = fm.__class__.getfixtureclosure(fm, fixturenames, parentnode)
# Compare with the previous behaviour TODO remove when in 'production' ?
# NOTE different order happens all the time because of our "prepend" strategy in the closure building
# which makes much more sense/intuition than pytest default
assert set(super_closure) == set(ref_fixturenames)
assert dict(arg2fixturedefs) == ref_arg2fixturedefs
return super_closure, arg2fixturedefs
else:
def getfixtureclosure(fm, fixturenames, parentnode, ignore_args=()):
"""
Replaces pytest's getfixtureclosure method to handle unions.
"""

# (2) now let's do it by ourselves to support fixture unions
_init_fixnames, super_closure, arg2fixturedefs = create_super_closure(fm, parentnode, fixturenames, ignore_args)
# (1) first retrieve the normal pytest output for comparison
kwargs = dict()
if PYTEST46_OR_GREATER:
# new argument "ignore_args" in 4.6+
kwargs['ignore_args'] = ignore_args

# Compare with the previous behaviour TODO remove when in 'production' ?
# NOTE different order happens all the time because of our "prepend" strategy in the closure building
# which makes much more sense/intuition than pytest default
assert set(super_closure) == set(ref_fixturenames)
assert dict(arg2fixturedefs) == ref_arg2fixturedefs
if PYTEST37_OR_GREATER:
# three outputs
initial_names, ref_fixturenames, ref_arg2fixturedefs = \
fm.__class__.getfixtureclosure(fm, fixturenames, parentnode, **kwargs)
else:
# two outputs
ref_fixturenames, ref_arg2fixturedefs = fm.__class__.getfixtureclosure(fm, fixturenames, parentnode)

if PYTEST37_OR_GREATER:
return _init_fixnames, super_closure, arg2fixturedefs
else:
return super_closure, arg2fixturedefs
# (2) now let's do it by ourselves to support fixture unions
_init_fixnames, super_closure, arg2fixturedefs = create_super_closure(fm, parentnode, fixturenames, ignore_args)

# Compare with the previous behaviour TODO remove when in 'production' ?
# NOTE different order happens all the time because of our "prepend" strategy in the closure building
# which makes much more sense/intuition than pytest default
assert set(super_closure) == set(ref_fixturenames)
assert dict(arg2fixturedefs) == ref_arg2fixturedefs

if PYTEST37_OR_GREATER:
return _init_fixnames, super_closure, arg2fixturedefs
else:
return super_closure, arg2fixturedefs


def create_super_closure(fm,
Expand Down Expand Up @@ -835,6 +854,11 @@ def _merge(new_items, into_list):
# we cannot sort yet - merge the fixture names into the _init_fixnames
_merge(fixturenames, _init_fixnames)

# Bugfix GH#330 in progress...
# TODO analyze why in the test "fixture_union_0simplest
# the first node contains second, and the second contains first
# or TODO check the test for get_callspecs, it is maybe simpler
Comment on lines +849 to +852
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current best 2 tracks


# Finally create the closure
fixture_defs_mgr = FixtureDefsCache(fm, parentnode)
closure_tree = FixtureClosureNode(fixture_defs_mgr=fixture_defs_mgr)
Expand Down Expand Up @@ -1035,7 +1059,8 @@ def create_call_list_from_pending_parametrizations(self):

if _DEBUG:
print("\n".join(["%s[%s]: funcargs=%s, params=%s" % (get_pytest_nodeid(self.metafunc),
c.id, c.funcargs, c.params)
c.id, c.params if PYTEST8_OR_GREATER else c.funcargs,
c.params)
for c in calls]) + "\n")

# clean EMPTY_ID set by @parametrize when there is at least a MultiParamsAlternative
Expand Down
Loading