11from collections import OrderedDict , namedtuple
2+ from copy import copy
23from distutils .version import LooseVersion
3- from functools import partial
4+ from warnings import warn
45
5- from _pytest . fixtures import scopes as pt_scopes
6+ from functools import partial
67
78import pytest
89
9- from pytest_cases .common import get_pytest_nodeid
10+ from pytest_cases .common import get_pytest_nodeid , get_parametrization_markers , get_pytest_function_scopenum , \
11+ is_function_node
1012from pytest_cases .main_fixtures import NOT_USED , is_fixture_union_params
1113
1214try : # python 3.3+
2224 pass
2325
2426
25- _DEBUG = True
27+ _DEBUG = False
2628
2729
2830# @hookspec(firstresult=True)
@@ -114,6 +116,14 @@ def __iter__(self):
114116 def __getitem__ (self , item ):
115117 return self .to_list ()[item ]
116118
119+ def __setitem__ (self , key , value ):
120+ # This is called in Pytest 4+. TODO how should we behave ?
121+ warn ("WARNING the new order is not taken into account !!" )
122+ pass
123+
124+ def index (self , * args ):
125+ return self .to_list ().index (* args )
126+
117127 def to_list (self ):
118128 """
119129 Converts self to a list to get all fixture names, and caches the result.
@@ -135,7 +145,7 @@ def sort_by_scope(arg_name):
135145 try :
136146 fixturedefs = self .get_all_fixture_defs ()[arg_name ]
137147 except KeyError :
138- return pt_scopes . index ( "function" )
148+ return get_pytest_function_scopenum ( )
139149 else :
140150 return fixturedefs [- 1 ].scopenum
141151 fixturenames_closure .sort (key = sort_by_scope )
@@ -319,7 +329,7 @@ def split_and_build(self,
319329
320330 # perform the propagation:
321331 # create a copy of the pending fixtures list and prepend the fixture used
322- pending_for_child = pending_fixtures_list . copy ()
332+ pending_for_child = copy (pending_fixtures_list )
323333 # (a) first propagate all child's dependencies
324334 new_c ._build_closure (fixture_defs_mgr , [f ])
325335 # (b) then the ones required by parent
@@ -383,6 +393,12 @@ def get_alternatives(self):
383393
384394
385395def merge (new_items , into_list ):
396+ """
397+ Appends items from `new_items` into `into_list`, only if they are not already there.
398+ :param new_items:
399+ :param into_list:
400+ :return:
401+ """
386402 at_least_one_added = False
387403 for l in new_items :
388404 if l not in into_list :
@@ -395,7 +411,7 @@ def getfixtureclosure(fm, fixturenames, parentnode):
395411
396412 # first retrieve the normal pytest output for comparison
397413 outputs = fm .__class__ .getfixtureclosure (fm , fixturenames , parentnode )
398- if LooseVersion (pytest .__version__ ) >= LooseVersion ('4.0 .0' ):
414+ if LooseVersion (pytest .__version__ ) >= LooseVersion ('3.10 .0' ):
399415 initial_names , ref_fixturenames , ref_arg2fixturedefs = outputs
400416 else :
401417 ref_fixturenames , ref_arg2fixturedefs = outputs
@@ -409,18 +425,23 @@ def getfixtureclosure(fm, fixturenames, parentnode):
409425
410426 # -- required fixtures/params.
411427 # ********* fix the order of initial fixtures: indeed this order may not be the right one ************
412- p_markers = list (parentnode .iter_markers (name = "parametrize" ))
413- cur_indices = []
414- for paramz_mark in p_markers :
415- param_names = paramz_mark .args [0 ].replace (' ' , '' ).split (',' )
416- for pname in param_names :
417- cur_indices .append (fixturenames .index (pname ))
418- target_indices = sorted (cur_indices )
419- sorted_fixturenames = list (fixturenames )
420- for old_i , new_i in zip (cur_indices , target_indices ):
421- sorted_fixturenames [new_i ] = fixturenames [old_i ]
422- # **********
423- merge (sorted_fixturenames , _init_fixnames )
428+ # this only works when pytest version is > 3.4, otherwise the parent node is a Module
429+ 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 ]
440+ # **********
441+ merge (sorted_fixturenames , _init_fixnames )
442+ else :
443+ # we cannot sort yet
444+ merge (fixturenames , _init_fixnames )
424445
425446 # Finally create the closure tree
426447 fixture_defs_mger = FixtureDefsCache (fm , parentid )
@@ -453,8 +474,9 @@ def getfixtureclosure(fm, fixturenames, parentnode):
453474 # store_union_closure_in_node(fixturenames_closure_node, parentnode)
454475
455476 # return ref_fixturenames, ref_arg2fixturedefs
456- if LooseVersion (pytest .__version__ ) >= LooseVersion ('4.0.0' ):
457- return initial_names , fixturenames_closure_node , ref_arg2fixturedefs
477+ if LooseVersion (pytest .__version__ ) >= LooseVersion ('3.10.0' ):
478+ our_initial_names = sorted_fixturenames # initial_names
479+ return our_initial_names , fixturenames_closure_node , ref_arg2fixturedefs
458480 else :
459481 return fixturenames_closure_node , ref_arg2fixturedefs
460482
@@ -638,11 +660,39 @@ def create_call_list_from_pending_parametrizations(self):
638660
639661 calls , nodes = self ._process_node (fix_closure_tree , pending .copy (), [])
640662
663+ self ._cleanup_calls_list (calls , nodes , pending )
664+
665+ if _DEBUG :
666+ print ("\n " .join (["%s[%s]: funcargs=%s, params=%s" % (get_pytest_nodeid (self .metafunc ),
667+ c .id , c .funcargs , c .params )
668+ for c in calls ]))
669+ print ()
670+
671+ self ._call_list = calls
672+
673+ # put back self as the _calls facade
674+ self .metafunc ._calls = bak_calls
675+
676+ # forget about all parametrizations now - this wont happen again
677+ self ._pending = None
678+
679+ def _cleanup_calls_list (self , calls , nodes , pending ):
680+ """
681+ Cleans the calls list so that all calls contain a value for all parameters. This is basically
682+ about adding "NOT_USED" parametrization everywhere relevant.
683+
684+ :param calls:
685+ :param nodes:
686+ :param pending:
687+ :return:
688+ """
689+
641690 nb_calls = len (calls )
642691 if nb_calls != len (nodes ):
643692 raise ValueError ("This should not happen !" )
644693
645- # Cleanup:
694+ function_scope_num = get_pytest_function_scopenum ()
695+
646696 for i in range (nb_calls ):
647697 c , n = calls [i ], nodes [i ]
648698
@@ -678,20 +728,8 @@ def create_call_list_from_pending_parametrizations(self):
678728 if fixture not in c .params and fixture not in c .funcargs :
679729 # explicitly add it as discarded by creating a parameter value for it.
680730 c .params [fixture ] = NOT_USED
681-
682- if _DEBUG :
683- print ("\n " .join (["%s[%s]: funcargs=%s, params=%s" % (get_pytest_nodeid (self .metafunc ),
684- c .id , c .funcargs , c .params )
685- for c in calls ]))
686- print ()
687-
688- self ._call_list = calls
689-
690- # put back self as the _calls facade
691- self .metafunc ._calls = bak_calls
692-
693- # forget about all parametrizations now - this wont happen again
694- self ._pending = None
731+ c .indices [fixture ] = 0
732+ c ._arg2scopenum [fixture ] = function_scope_num
695733
696734 def _parametrize_calls (self , init_calls , argnames , argvalues , discard_id = False , indirect = False , ids = None ,
697735 scope = None , ** kwargs ):
0 commit comments