9
9
import os
10
10
import sys
11
11
import warnings
12
+ from functools import partial
12
13
from textwrap import dedent
13
14
14
15
import py
@@ -435,9 +436,66 @@ def _getobj(self):
435
436
return self ._importtestmodule ()
436
437
437
438
def collect (self ):
439
+ self ._inject_setup_module_fixture ()
440
+ self ._inject_setup_function_fixture ()
438
441
self .session ._fixturemanager .parsefactories (self )
439
442
return super (Module , self ).collect ()
440
443
444
+ def _inject_setup_module_fixture (self ):
445
+ """Injects a hidden autouse, module scoped fixture into the collected module object
446
+ that invokes setUpModule/tearDownModule if either or both are available.
447
+
448
+ Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
449
+ other fixtures (#517).
450
+ """
451
+ setup_module = _get_non_fixture_func (self .obj , "setUpModule" )
452
+ if setup_module is None :
453
+ setup_module = _get_non_fixture_func (self .obj , "setup_module" )
454
+
455
+ teardown_module = _get_non_fixture_func (self .obj , "tearDownModule" )
456
+ if teardown_module is None :
457
+ teardown_module = _get_non_fixture_func (self .obj , "teardown_module" )
458
+
459
+ if setup_module is None and teardown_module is None :
460
+ return
461
+
462
+ @fixtures .fixture (autouse = True , scope = "module" )
463
+ def xunit_setup_module_fixture (request ):
464
+ if setup_module is not None :
465
+ _call_with_optional_argument (setup_module , request .module )
466
+ yield
467
+ if teardown_module is not None :
468
+ _call_with_optional_argument (teardown_module , request .module )
469
+
470
+ self .obj .__pytest_setup_module = xunit_setup_module_fixture
471
+
472
+ def _inject_setup_function_fixture (self ):
473
+ """Injects a hidden autouse, function scoped fixture into the collected module object
474
+ that invokes setup_function/teardown_function if either or both are available.
475
+
476
+ Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
477
+ other fixtures (#517).
478
+ """
479
+ setup_function = _get_non_fixture_func (self .obj , "setup_function" )
480
+ teardown_function = _get_non_fixture_func (self .obj , "teardown_function" )
481
+ if setup_function is None and teardown_function is None :
482
+ return
483
+
484
+ @fixtures .fixture (autouse = True , scope = "function" )
485
+ def xunit_setup_function_fixture (request ):
486
+ if request .instance is not None :
487
+ # in this case we are bound to an instance, so we need to let
488
+ # setup_method handle this
489
+ yield
490
+ return
491
+ if setup_function is not None :
492
+ _call_with_optional_argument (setup_function , request .function )
493
+ yield
494
+ if teardown_function is not None :
495
+ _call_with_optional_argument (teardown_function , request .function )
496
+
497
+ self .obj .__pytest_setup_function = xunit_setup_function_fixture
498
+
441
499
def _importtestmodule (self ):
442
500
# we assume we are only called once per module
443
501
importmode = self .config .getoption ("--import-mode" )
@@ -488,19 +546,6 @@ def _importtestmodule(self):
488
546
self .config .pluginmanager .consider_module (mod )
489
547
return mod
490
548
491
- def setup (self ):
492
- setup_module = _get_xunit_setup_teardown (self .obj , "setUpModule" )
493
- if setup_module is None :
494
- setup_module = _get_xunit_setup_teardown (self .obj , "setup_module" )
495
- if setup_module is not None :
496
- setup_module ()
497
-
498
- teardown_module = _get_xunit_setup_teardown (self .obj , "tearDownModule" )
499
- if teardown_module is None :
500
- teardown_module = _get_xunit_setup_teardown (self .obj , "teardown_module" )
501
- if teardown_module is not None :
502
- self .addfinalizer (teardown_module )
503
-
504
549
505
550
class Package (Module ):
506
551
def __init__ (self , fspath , parent = None , config = None , session = None , nodeid = None ):
@@ -513,6 +558,22 @@ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
513
558
self ._norecursepatterns = session ._norecursepatterns
514
559
self .fspath = fspath
515
560
561
+ def setup (self ):
562
+ # not using fixtures to call setup_module here because autouse fixtures
563
+ # from packages are not called automatically (#4085)
564
+ setup_module = _get_non_fixture_func (self .obj , "setUpModule" )
565
+ if setup_module is None :
566
+ setup_module = _get_non_fixture_func (self .obj , "setup_module" )
567
+ if setup_module is not None :
568
+ _call_with_optional_argument (setup_module , self .obj )
569
+
570
+ teardown_module = _get_non_fixture_func (self .obj , "tearDownModule" )
571
+ if teardown_module is None :
572
+ teardown_module = _get_non_fixture_func (self .obj , "teardown_module" )
573
+ if teardown_module is not None :
574
+ func = partial (_call_with_optional_argument , teardown_module , self .obj )
575
+ self .addfinalizer (func )
576
+
516
577
def _recurse (self , dirpath ):
517
578
if dirpath .basename == "__pycache__" :
518
579
return False
@@ -599,8 +660,9 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
599
660
when the callable is called without arguments, defaults to the ``holder`` object.
600
661
Return ``None`` if a suitable callable is not found.
601
662
"""
663
+ # TODO: only needed because of Package!
602
664
param_obj = param_obj if param_obj is not None else holder
603
- result = _get_xunit_func (holder , attr_name )
665
+ result = _get_non_fixture_func (holder , attr_name )
604
666
if result is not None :
605
667
arg_count = result .__code__ .co_argcount
606
668
if inspect .ismethod (result ):
@@ -611,7 +673,19 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
611
673
return result
612
674
613
675
614
- def _get_xunit_func (obj , name ):
676
+ def _call_with_optional_argument (func , arg ):
677
+ """Call the given function with the given argument if func accepts one argument, otherwise
678
+ calls func without arguments"""
679
+ arg_count = func .__code__ .co_argcount
680
+ if inspect .ismethod (func ):
681
+ arg_count -= 1
682
+ if arg_count :
683
+ func (arg )
684
+ else :
685
+ func ()
686
+
687
+
688
+ def _get_non_fixture_func (obj , name ):
615
689
"""Return the attribute from the given object to be used as a setup/teardown
616
690
xunit-style function, but only if not marked as a fixture to
617
691
avoid calling it twice.
@@ -643,18 +717,60 @@ def collect(self):
643
717
)
644
718
)
645
719
return []
720
+
721
+ self ._inject_setup_class_fixture ()
722
+ self ._inject_setup_method_fixture ()
723
+
646
724
return [Instance (name = "()" , parent = self )]
647
725
648
- def setup (self ):
649
- setup_class = _get_xunit_func (self .obj , "setup_class" )
650
- if setup_class is not None :
651
- setup_class = getimfunc (setup_class )
652
- setup_class (self .obj )
726
+ def _inject_setup_class_fixture (self ):
727
+ """Injects a hidden autouse, class scoped fixture into the collected class object
728
+ that invokes setup_class/teardown_class if either or both are available.
653
729
654
- fin_class = getattr (self .obj , "teardown_class" , None )
655
- if fin_class is not None :
656
- fin_class = getimfunc (fin_class )
657
- self .addfinalizer (lambda : fin_class (self .obj ))
730
+ Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
731
+ other fixtures (#517).
732
+ """
733
+ setup_class = _get_non_fixture_func (self .obj , "setup_class" )
734
+ teardown_class = getattr (self .obj , "teardown_class" , None )
735
+ if setup_class is None and teardown_class is None :
736
+ return
737
+
738
+ @fixtures .fixture (autouse = True , scope = "class" )
739
+ def xunit_setup_class_fixture (cls ):
740
+ if setup_class is not None :
741
+ func = getimfunc (setup_class )
742
+ _call_with_optional_argument (func , self .obj )
743
+ yield
744
+ if teardown_class is not None :
745
+ func = getimfunc (teardown_class )
746
+ _call_with_optional_argument (func , self .obj )
747
+
748
+ self .obj .__pytest_setup_class = xunit_setup_class_fixture
749
+
750
+ def _inject_setup_method_fixture (self ):
751
+ """Injects a hidden autouse, function scoped fixture into the collected class object
752
+ that invokes setup_method/teardown_method if either or both are available.
753
+
754
+ Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
755
+ other fixtures (#517).
756
+ """
757
+ setup_method = _get_non_fixture_func (self .obj , "setup_method" )
758
+ teardown_method = getattr (self .obj , "teardown_method" , None )
759
+ if setup_method is None and teardown_method is None :
760
+ return
761
+
762
+ @fixtures .fixture (autouse = True , scope = "function" )
763
+ def xunit_setup_method_fixture (self , request ):
764
+ method = request .function
765
+ if setup_method is not None :
766
+ func = getattr (self , "setup_method" )
767
+ _call_with_optional_argument (func , method )
768
+ yield
769
+ if teardown_method is not None :
770
+ func = getattr (self , "teardown_method" )
771
+ _call_with_optional_argument (func , method )
772
+
773
+ self .obj .__pytest_setup_method = xunit_setup_method_fixture
658
774
659
775
660
776
class Instance (PyCollector ):
@@ -681,29 +797,9 @@ class FunctionMixin(PyobjMixin):
681
797
682
798
def setup (self ):
683
799
""" perform setup for this test function. """
684
- if hasattr (self , "_preservedparent" ):
685
- obj = self ._preservedparent
686
- elif isinstance (self .parent , Instance ):
687
- obj = self .parent .newinstance ()
800
+ if isinstance (self .parent , Instance ):
801
+ self .parent .newinstance ()
688
802
self .obj = self ._getobj ()
689
- else :
690
- obj = self .parent .obj
691
- if inspect .ismethod (self .obj ):
692
- setup_name = "setup_method"
693
- teardown_name = "teardown_method"
694
- else :
695
- setup_name = "setup_function"
696
- teardown_name = "teardown_function"
697
- setup_func_or_method = _get_xunit_setup_teardown (
698
- obj , setup_name , param_obj = self .obj
699
- )
700
- if setup_func_or_method is not None :
701
- setup_func_or_method ()
702
- teardown_func_or_method = _get_xunit_setup_teardown (
703
- obj , teardown_name , param_obj = self .obj
704
- )
705
- if teardown_func_or_method is not None :
706
- self .addfinalizer (teardown_func_or_method )
707
803
708
804
def _prunetraceback (self , excinfo ):
709
805
if hasattr (self , "_obj" ) and not self .config .option .fulltrace :
0 commit comments