Skip to content

Commit bdcb0f0

Browse files
author
Sylvain MARIE
committed
Fixed error with pytest 4.6+: ignore_args argument
1 parent e3739e5 commit bdcb0f0

File tree

2 files changed

+89
-50
lines changed

2 files changed

+89
-50
lines changed

pytest_cases/common.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,44 @@ def __init__(self, *args, **kwargs):
7474
self.kwargs = kwargs
7575

7676

77+
# ---------------- working on pytest nodes (e.g. Function)
78+
79+
def is_function_node(node):
80+
try:
81+
node.function
82+
return True
83+
except AttributeError:
84+
return False
85+
86+
87+
def get_parametrization_markers(fnode):
88+
"""
89+
Returns the parametrization marks on a pytest Function node.
90+
:param fnode:
91+
:return:
92+
"""
93+
if LooseVersion(pytest.__version__) >= LooseVersion('3.4.0'):
94+
return list(fnode.iter_markers(name="parametrize"))
95+
else:
96+
return list(fnode.parametrize)
97+
98+
99+
def get_param_names(fnode):
100+
"""
101+
Returns a list of parameter names for the given pytest Function node.
102+
parameterization marks containing several names are split
103+
104+
:param parentnode:
105+
:return:
106+
"""
107+
p_markers = get_parametrization_markers(fnode)
108+
param_names = []
109+
for paramz_mark in p_markers:
110+
param_names += paramz_mark.args[0].replace(' ', '').split(',')
111+
return param_names
112+
113+
114+
# ---------------- working on functions
77115
def get_pytest_marks_on_function(f):
78116
"""
79117
Utility to return *ALL* pytest marks (not only parametrization) applied on a function
@@ -92,27 +130,6 @@ def get_pytest_marks_on_function(f):
92130
return []
93131

94132

95-
def is_function_node(node):
96-
try:
97-
node.function
98-
return True
99-
except AttributeError:
100-
return False
101-
102-
103-
def get_parametrization_markers(node):
104-
"""
105-
Returns the parametrization marks on a function node.
106-
Not sure how different this one is from the ones below, probably the input is different
107-
:param node:
108-
:return:
109-
"""
110-
if LooseVersion(pytest.__version__) >= LooseVersion('3.4.0'):
111-
return list(node.iter_markers(name="parametrize"))
112-
else:
113-
return list(node.parametrize)
114-
115-
116133
def get_pytest_parametrize_marks(f):
117134
"""
118135
Returns the @pytest.mark.parametrize marks associated with a function (and only those)

pytest_cases/plugin.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import pytest
99

10-
from pytest_cases.common import get_pytest_nodeid, get_parametrization_markers, get_pytest_function_scopenum, \
11-
is_function_node
10+
from pytest_cases.common import get_pytest_nodeid, get_pytest_function_scopenum, \
11+
is_function_node, get_param_names
1212
from pytest_cases.main_fixtures import NOT_USED, is_fixture_union_params
1313

1414
try: # python 3.3+
@@ -407,14 +407,21 @@ def merge(new_items, into_list):
407407
return at_least_one_added
408408

409409

410-
def getfixtureclosure(fm, fixturenames, parentnode):
410+
def getfixtureclosure(fm, fixturenames, parentnode, ignore_args=()):
411411

412412
# first retrieve the normal pytest output for comparison
413-
outputs = fm.__class__.getfixtureclosure(fm, fixturenames, parentnode)
413+
kwargs = dict()
414+
if LooseVersion(pytest.__version__) >= LooseVersion('4.6.0'):
415+
# new argument "ignore_args" in 4.6+
416+
kwargs['ignore_args'] = ignore_args
417+
414418
if LooseVersion(pytest.__version__) >= LooseVersion('3.10.0'):
415-
initial_names, ref_fixturenames, ref_arg2fixturedefs = outputs
419+
# three outputs
420+
initial_names, ref_fixturenames, ref_arg2fixturedefs = \
421+
fm.__class__.getfixtureclosure(fm, fixturenames, parentnode, **kwargs)
416422
else:
417-
ref_fixturenames, ref_arg2fixturedefs = outputs
423+
# two outputs
424+
ref_fixturenames, ref_arg2fixturedefs = fm.__class__.getfixtureclosure(fm, fixturenames, parentnode)
418425

419426
# now let's do it by ourselves.
420427
parentid = parentnode.nodeid
@@ -427,16 +434,11 @@ def getfixtureclosure(fm, fixturenames, parentnode):
427434
# ********* fix the order of initial fixtures: indeed this order may not be the right one ************
428435
# this only works when pytest version is > 3.4, otherwise the parent node is a Module
429436
if is_function_node(parentnode):
430-
p_markers = get_parametrization_markers(parentnode)
431-
cur_indices = []
432-
for paramz_mark in p_markers:
433-
param_names = paramz_mark.args[0].replace(' ', '').split(',')
434-
for pname in param_names:
435-
cur_indices.append(fixturenames.index(pname))
436-
target_indices = sorted(cur_indices)
437-
sorted_fixturenames = list(fixturenames)
438-
for old_i, new_i in zip(cur_indices, target_indices):
439-
sorted_fixturenames[new_i] = fixturenames[old_i]
437+
# grab all the parametrization on that node and fix the order.
438+
# Note: on pytest >= 4 the list of param_names is probably the same than the `ignore_args` input
439+
param_names = get_param_names(parentnode)
440+
441+
sorted_fixturenames = sort_according_to_ref_list(fixturenames, param_names)
440442
# **********
441443
merge(sorted_fixturenames, _init_fixnames)
442444
else:
@@ -456,24 +458,24 @@ def getfixtureclosure(fm, fixturenames, parentnode):
456458
fixturenames_closure_node.to_list()
457459

458460
# FINALLY compare with the previous behaviour TODO remove when in 'production' ?
459-
assert fixturenames_closure_node.get_all_fixture_defs() == ref_arg2fixturedefs
460-
# if fixturenames_closure_node.has_split():
461-
# # order might be changed
462-
# assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
463-
# else:
464-
# # same order
465-
# if len(p_markers) < 2:
466-
# assert list(fixturenames_closure_node) == ref_fixturenames
467-
# else:
468-
# NOW different order happens all the time because of the "prepend" strategy in the closure building
469-
# which makes much more sense/intuition.
470-
assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
461+
if len(ignore_args) == 0:
462+
assert fixturenames_closure_node.get_all_fixture_defs() == ref_arg2fixturedefs
463+
# if fixturenames_closure_node.has_split():
464+
# # order might be changed
465+
# assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
466+
# else:
467+
# # same order
468+
# if len(p_markers) < 2:
469+
# assert list(fixturenames_closure_node) == ref_fixturenames
470+
# else:
471+
# NOW different order happens all the time because of the "prepend" strategy in the closure building
472+
# which makes much more sense/intuition.
473+
assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
471474

472475
# and store our closure in the node
473476
# note as an alternative we could return a custom object in place of the ref_fixturenames
474477
# store_union_closure_in_node(fixturenames_closure_node, parentnode)
475478

476-
# return ref_fixturenames, ref_arg2fixturedefs
477479
if LooseVersion(pytest.__version__) >= LooseVersion('3.10.0'):
478480
our_initial_names = sorted_fixturenames # initial_names
479481
return our_initial_names, fixturenames_closure_node, ref_arg2fixturedefs
@@ -872,3 +874,23 @@ def _first_time_met(v):
872874

873875
def flatten_list(lst):
874876
return [v for nested_list in lst for v in nested_list]
877+
878+
879+
def sort_according_to_ref_list(fixturenames, param_names):
880+
"""
881+
Sorts items in the first list, according to their position in the second.
882+
Items that are not in the second list stay in the same position, the others are just swapped.
883+
A new list is returned.
884+
885+
:param fixturenames:
886+
:param param_names:
887+
:return:
888+
"""
889+
cur_indices = []
890+
for pname in param_names:
891+
cur_indices.append(fixturenames.index(pname))
892+
target_indices = sorted(cur_indices)
893+
sorted_fixturenames = list(fixturenames)
894+
for old_i, new_i in zip(cur_indices, target_indices):
895+
sorted_fixturenames[new_i] = fixturenames[old_i]
896+
return sorted_fixturenames

0 commit comments

Comments
 (0)