2
2
import os
3
3
import sys
4
4
import types
5
+ from functools import partial
5
6
from pathlib import Path
6
7
from typing import Dict
7
8
from typing import List
19
20
from _pytest .outcomes import OutcomeException
20
21
from _pytest .pytester import Pytester
21
22
23
+ if sys .version_info [:2 ] < (3 , 11 ):
24
+ from exceptiongroup import ExceptionGroup
25
+
22
26
23
27
class TestSetupState :
24
28
def test_setup (self , pytester : Pytester ) -> None :
@@ -77,8 +81,6 @@ def fin3():
77
81
assert r == ["fin3" , "fin1" ]
78
82
79
83
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.
82
84
def fin1 ():
83
85
raise Exception ("oops1" )
84
86
@@ -90,9 +92,14 @@ def fin2():
90
92
ss .setup (item )
91
93
ss .addfinalizer (fin1 , item )
92
94
ss .addfinalizer (fin2 , item )
93
- with pytest .raises (Exception ) as err :
95
+ with pytest .raises (ExceptionGroup ) as err :
94
96
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" ,)
96
103
97
104
def test_teardown_multiple_scopes_one_fails (self , pytester : Pytester ) -> None :
98
105
module_teardown = []
@@ -113,6 +120,25 @@ def fin_module():
113
120
ss .teardown_exact (None )
114
121
assert module_teardown == ["fin_module" ]
115
122
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
+
116
142
117
143
class BaseFunctionalTests :
118
144
def test_passfunction (self , pytester : Pytester ) -> None :
0 commit comments