8
8
import sys
9
9
import warnings
10
10
11
+ import re
12
+
11
13
from _pytest .fixtures import yield_fixture
12
14
from _pytest .outcomes import fail
13
15
@@ -98,23 +100,41 @@ def warns(expected_warning, *args, **kwargs):
98
100
99
101
>>> with warns(RuntimeWarning):
100
102
... warnings.warn("my warning", RuntimeWarning)
103
+
104
+ In the context manager form you may use the keyword argument ``match`` to assert
105
+ that the exception matches a text or regex::
106
+
107
+ >>> with warns(UserWarning, match='must be 0 or None'):
108
+ ... warnings.warn("value must be 0 or None", UserWarning)
109
+
110
+ >>> with warns(UserWarning, match=r'must be \d+$'):
111
+ ... warnings.warn("value must be 42", UserWarning)
112
+
113
+ >>> with warns(UserWarning, match=r'must be \d+$'):
114
+ ... warnings.warn("this is not here", UserWarning)
115
+ Traceback (most recent call last):
116
+ ...
117
+ Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
118
+
101
119
"""
102
- wcheck = WarningsChecker ( expected_warning )
120
+ match_expr = None
103
121
if not args :
104
- return wcheck
122
+ if "match" in kwargs :
123
+ match_expr = kwargs .pop ("match" )
124
+ return WarningsChecker (expected_warning , match_expr = match_expr )
105
125
elif isinstance (args [0 ], str ):
106
126
code , = args
107
127
assert isinstance (code , str )
108
128
frame = sys ._getframe (1 )
109
129
loc = frame .f_locals .copy ()
110
130
loc .update (kwargs )
111
131
112
- with wcheck :
132
+ with WarningsChecker ( expected_warning , match_expr = match_expr ) :
113
133
code = _pytest ._code .Source (code ).compile ()
114
134
py .builtin .exec_ (code , frame .f_globals , loc )
115
135
else :
116
136
func = args [0 ]
117
- with wcheck :
137
+ with WarningsChecker ( expected_warning , match_expr = match_expr ) :
118
138
return func (* args [1 :], ** kwargs )
119
139
120
140
@@ -174,7 +194,7 @@ def __exit__(self, *exc_info):
174
194
175
195
176
196
class WarningsChecker (WarningsRecorder ):
177
- def __init__ (self , expected_warning = None ):
197
+ def __init__ (self , expected_warning = None , match_expr = None ):
178
198
super (WarningsChecker , self ).__init__ ()
179
199
180
200
msg = ("exceptions must be old-style classes or "
@@ -189,6 +209,7 @@ def __init__(self, expected_warning=None):
189
209
raise TypeError (msg % type (expected_warning ))
190
210
191
211
self .expected_warning = expected_warning
212
+ self .match_expr = match_expr
192
213
193
214
def __exit__ (self , * exc_info ):
194
215
super (WarningsChecker , self ).__exit__ (* exc_info )
@@ -203,3 +224,15 @@ def __exit__(self, *exc_info):
203
224
"The list of emitted warnings is: {1}." .format (
204
225
self .expected_warning ,
205
226
[each .message for each in self ]))
227
+ elif self .match_expr is not None :
228
+ for r in self :
229
+ if issubclass (r .category , self .expected_warning ):
230
+ if re .compile (self .match_expr ).search (str (r .message )):
231
+ break
232
+ else :
233
+ fail ("DID NOT WARN. No warnings of type {0} matching"
234
+ " ('{1}') was emitted. The list of emitted warnings"
235
+ " is: {2}." .format (
236
+ self .expected_warning ,
237
+ self .match_expr ,
238
+ [each .message for each in self ]))
0 commit comments