22import os
33import sys
44import types
5+ from functools import partial
56from pathlib import Path
67from typing import Dict
78from typing import List
1920from _pytest .outcomes import OutcomeException
2021from _pytest .pytester import Pytester
2122
23+ if sys .version_info [:2 ] < (3 , 11 ):
24+ from exceptiongroup import ExceptionGroup
25+
2226
2327class TestSetupState :
2428 def test_setup (self , pytester : Pytester ) -> None :
@@ -77,8 +81,6 @@ def fin3():
7781 assert r == ["fin3" , "fin1" ]
7882
7983 def test_teardown_multiple_fail (self , pytester : Pytester ) -> None :
80- # Ensure the first exception is the one which is re-raised.
81- # Ideally both would be reported however.
8284 def fin1 ():
8385 raise Exception ("oops1" )
8486
@@ -90,9 +92,14 @@ def fin2():
9092 ss .setup (item )
9193 ss .addfinalizer (fin1 , item )
9294 ss .addfinalizer (fin2 , item )
93- with pytest .raises (Exception ) as err :
95+ with pytest .raises (ExceptionGroup ) as err :
9496 ss .teardown_exact (None )
95- assert err .value .args == ("oops2" ,)
97+
98+ # Note that finalizers are run LIFO, but because FIFO is more intuitive for
99+ # users we reverse the order of messages, and see the error from fin1 first.
100+ err1 , err2 = err .value .exceptions
101+ assert err1 .args == ("oops1" ,)
102+ assert err2 .args == ("oops2" ,)
96103
97104 def test_teardown_multiple_scopes_one_fails (self , pytester : Pytester ) -> None :
98105 module_teardown = []
@@ -113,6 +120,25 @@ def fin_module():
113120 ss .teardown_exact (None )
114121 assert module_teardown == ["fin_module" ]
115122
123+ def test_teardown_multiple_scopes_several_fail (self , pytester ) -> None :
124+ def raiser (exc ):
125+ raise exc
126+
127+ item = pytester .getitem ("def test_func(): pass" )
128+ mod = item .listchain ()[- 2 ]
129+ ss = item .session ._setupstate
130+ ss .setup (item )
131+ ss .addfinalizer (partial (raiser , KeyError ("from module scope" )), mod )
132+ ss .addfinalizer (partial (raiser , TypeError ("from function scope 1" )), item )
133+ ss .addfinalizer (partial (raiser , ValueError ("from function scope 2" )), item )
134+
135+ with pytest .raises (ExceptionGroup , match = "errors during test teardown" ) as e :
136+ ss .teardown_exact (None )
137+ mod , func = e .value .exceptions
138+ assert isinstance (mod , KeyError )
139+ assert isinstance (func .exceptions [0 ], TypeError ) # type: ignore
140+ assert isinstance (func .exceptions [1 ], ValueError ) # type: ignore
141+
116142
117143class BaseFunctionalTests :
118144 def test_passfunction (self , pytester : Pytester ) -> None :
0 commit comments