@@ -61,9 +61,20 @@ def get_fixture_defs(self, fixname):
6161class FixtureClosureNode (object ):
6262 __slots__ = 'parent' , 'fixture_defs' , \
6363 'split_fixture_name' , 'split_fixture_discarded_names' , 'children' , \
64- '_as_list' , 'all_fixture_defs'
64+ '_as_list' , 'all_fixture_defs' , 'fixture_defs_mgr'
65+
66+ def __init__ (self ,
67+ fixture_defs_mgr = None , # type: FixtureDefsCache
68+ parent_node = None # type: FixtureClosureNode
69+ ):
70+ if fixture_defs_mgr is None :
71+ if parent_node is None :
72+ raise ValueError ()
73+ fixture_defs_mgr = parent_node .fixture_defs_mgr
74+ else :
75+ assert isinstance (fixture_defs_mgr , FixtureDefsCache )
6576
66- def __init__ ( self , parent_node = None ):
77+ self . fixture_defs_mgr = fixture_defs_mgr
6778 self .parent = parent_node
6879
6980 # these will be set after closure has been built
@@ -129,7 +140,15 @@ def __setitem__(self, key, value):
129140 warn ("WARNING the new order is not taken into account !!" )
130141
131142 def append (self , item ):
132- warn ("WARNING some code tries to add an item to the fixture tree, this will be IGNORED !! Item: %s" % item )
143+ """
144+ This is now supported: we simply update the closure with the new item.
145+ `self.all_fixture_defs` and `self._as_list` are updated on the way
146+ so the resulting facades used by pytest are consistent after the update.
147+
148+ :param item:
149+ :return:
150+ """
151+ self .build_closure ((item , ))
133152
134153 def insert (self , index , object ):
135154 warn ("WARNING some code tries to insert an item in the fixture tree, this will be IGNORED !! "
@@ -224,10 +243,36 @@ def _get_all_fixture_defs(self):
224243 # ---- utils to build the closure
225244
226245 def build_closure (self ,
227- fixture_defs_mgr , # type: FixtureDefsCache
228- initial_fixture_names # type: Iterable[str]
246+ initial_fixture_names , # type: Iterable[str]
247+ ignore_args = ()
229248 ):
230- self ._build_closure (fixture_defs_mgr , initial_fixture_names )
249+ """
250+ Updates this Node with the fixture names provided as argument.
251+ Fixture names and definitions will be stored in self.fixture_defs.
252+
253+ If some fixtures are Union fixtures, this node will become a "split" node
254+ and have children. If new fixtures are added to the closure after that,
255+ they will be added to the child nodes rather than self.
256+
257+ Note: when this method is used on an existing (already filled) root node,
258+ all of its internal structures (self._as_list and self.all_fixture_defs) are updated accordingly so that the
259+ facades used by pytest are still consistent.
260+
261+ :param initial_fixture_names:
262+ :param ignore_args: arguments to keep in the names but not to put in the fixture defs, because they correspond
263+ "direct parametrize"
264+ :return:
265+ """
266+ self ._build_closure (self .fixture_defs_mgr , initial_fixture_names , ignore_args = ignore_args )
267+
268+ # update fixture defs
269+ if self .all_fixture_defs is None :
270+ self .all_fixture_defs = self ._get_all_fixture_defs ()
271+ else :
272+ self .all_fixture_defs .update (self ._get_all_fixture_defs ())
273+
274+ # mark the fixture list as to be rebuilt (automatic next time one iterates on self)
275+ self ._as_list = None
231276
232277 def is_closure_built (self ):
233278 return self .fixture_defs is not None
@@ -242,12 +287,15 @@ def already_knows_fixture(self, fixture_name):
242287 return self .parent .already_knows_fixture (fixture_name )
243288
244289 def _build_closure (self ,
245- fixture_defs_mgr , # type: FixtureDefsCache
246- initial_fixture_names # type: Iterable[str]
290+ fixture_defs_mgr , # type: FixtureDefsCache
291+ initial_fixture_names , # type: Iterable[str]
292+ ignore_args
247293 ):
248294 """
249295
250- :param arg2fixturedefs: set of fixtures already known by the parent node
296+ :param fixture_defs_mgr:
297+ :param initial_fixture_names:
298+ :param ignore_args: arguments to keep in the names but not to put in the fixture defs
251299 :return: nothing (the input arg2fixturedefs is modified)
252300 """
253301
@@ -267,6 +315,11 @@ def _build_closure(self,
267315 if self .already_knows_fixture (fixname ):
268316 continue
269317
318+ # new ignore_args option in pytest 4.6+
319+ if fixname in ignore_args :
320+ self .add_required_fixture (fixname , None )
321+ continue
322+
270323 # else grab the fixture definition(s) for this fixture name for this test node id
271324 fixturedefs = fixture_defs_mgr .get_fixture_defs (fixname )
272325 if not fixturedefs :
@@ -289,7 +342,7 @@ def _build_closure(self,
289342
290343 # propagate WITH the pending
291344 self .split_and_build (fixture_defs_mgr , fixname , fixturedefs , alternative_f_names ,
292- pending_fixture_names )
345+ pending_fixture_names , ignore_args = ignore_args )
293346
294347 # empty the pending
295348 pending_fixture_names = []
@@ -325,7 +378,8 @@ def split_and_build(self,
325378 split_fixture_name , # type: str
326379 split_fixture_defs , # type: Tuple[FixtureDefinition]
327380 alternative_fixture_names , # type: List[str]
328- pending_fixtures_list #
381+ pending_fixtures_list , #
382+ ignore_args
329383 ):
330384 """ Declares that this node contains a union with alternatives (child nodes=subtrees) """
331385
@@ -344,7 +398,7 @@ def split_and_build(self,
344398 # create the child nodes
345399 for f in alternative_fixture_names :
346400 # create the child node
347- new_c = FixtureClosureNode (self )
401+ new_c = FixtureClosureNode (parent_node = self )
348402 self .children [f ] = new_c
349403
350404 # set the discarded fixture names
@@ -354,9 +408,9 @@ def split_and_build(self,
354408 # create a copy of the pending fixtures list and prepend the fixture used
355409 pending_for_child = copy (pending_fixtures_list )
356410 # (a) first propagate all child's dependencies
357- new_c ._build_closure (fixture_defs_mgr , [f ])
411+ new_c ._build_closure (fixture_defs_mgr , [f ], ignore_args = ignore_args )
358412 # (b) then the ones required by parent
359- new_c ._build_closure (fixture_defs_mgr , pending_for_child )
413+ new_c ._build_closure (fixture_defs_mgr , pending_for_child , ignore_args = ignore_args )
360414
361415 def has_split (self ):
362416 return self .split_fixture_name is not None
@@ -522,8 +576,8 @@ def getfixtureclosure(fm, fixturenames, parentnode, ignore_args=()):
522576 print ("Creating closure for %s:" % parentid )
523577
524578 fixture_defs_mger = FixtureDefsCache (fm , parentid )
525- fixturenames_closure_node = FixtureClosureNode ()
526- fixturenames_closure_node .build_closure (fixture_defs_mger , _init_fixnames )
579+ fixturenames_closure_node = FixtureClosureNode (fixture_defs_mgr = fixture_defs_mger )
580+ fixturenames_closure_node .build_closure (_init_fixnames , ignore_args = ignore_args )
527581
528582 if _DEBUG :
529583 print ("Closure for %s completed:" % parentid )
@@ -533,29 +587,30 @@ def getfixtureclosure(fm, fixturenames, parentnode, ignore_args=()):
533587 fixturenames_closure_node .to_list ()
534588
535589 # FINALLY compare with the previous behaviour TODO remove when in 'production' ?
536- if len (ignore_args ) == 0 :
537- assert fixturenames_closure_node .get_all_fixture_defs () == ref_arg2fixturedefs
538- # if fixturenames_closure_node.has_split():
539- # # order might be changed
540- # assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
541- # else:
542- # # same order
543- # if len(p_markers) < 2:
544- # assert list(fixturenames_closure_node) == ref_fixturenames
545- # else:
546- # NOW different order happens all the time because of the "prepend" strategy in the closure building
547- # which makes much more sense/intuition.
548- assert set ((str (f ) for f in fixturenames_closure_node )) == set (ref_fixturenames )
590+ arg2fixturedefs = fixturenames_closure_node .get_all_fixture_defs ()
591+ # if len(ignore_args) == 0:
592+ assert dict (arg2fixturedefs ) == ref_arg2fixturedefs
593+ # if fixturenames_closure_node.has_split():
594+ # # order might be changed
595+ # assert set((str(f) for f in fixturenames_closure_node)) == set(ref_fixturenames)
596+ # else:
597+ # # same order
598+ # if len(p_markers) < 2:
599+ # assert list(fixturenames_closure_node) == ref_fixturenames
600+ # else:
601+ # NOW different order happens all the time because of the "prepend" strategy in the closure building
602+ # which makes much more sense/intuition.
603+ assert set ((str (f ) for f in fixturenames_closure_node )) == set (ref_fixturenames )
549604
550605 # and store our closure in the node
551606 # note as an alternative we could return a custom object in place of the ref_fixturenames
552607 # store_union_closure_in_node(fixturenames_closure_node, parentnode)
553608
554609 if LooseVersion (pytest .__version__ ) >= LooseVersion ('3.7.0' ):
555610 our_initial_names = sorted_fixturenames # initial_names
556- return our_initial_names , fixturenames_closure_node , ref_arg2fixturedefs
611+ return our_initial_names , fixturenames_closure_node , arg2fixturedefs
557612 else :
558- return fixturenames_closure_node , ref_arg2fixturedefs
613+ return fixturenames_closure_node , arg2fixturedefs
559614
560615
561616# ------------ hack to store and retrieve our custom "closure" object
0 commit comments