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