1
# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
2
# See LICENSE for details.
5
Tests for Trial's interaction with the Python warning system.
9
from StringIO import StringIO
11
from twisted.python.filepath import FilePath
12
from twisted.trial.unittest import TestCase, _collectWarnings
13
from twisted.trial.reporter import TestResult
17
Hide a L{TestCase} definition from trial's automatic discovery mechanism.
19
class MockTests(TestCase):
21
A test case which is used by L{FlushWarningsTests} to verify behavior
22
which cannot be verified by code inside a single test method.
24
message = "some warning text"
25
category = UserWarning
27
def test_unflushed(self):
29
Generate a warning and don't flush it.
31
warnings.warn(self.message, self.category)
34
def test_flushed(self):
36
Generate a warning and flush it.
38
warnings.warn(self.message, self.category)
39
self.assertEqual(len(self.flushWarnings()), 1)
43
class FlushWarningsTests(TestCase):
45
Tests for L{TestCase.flushWarnings}, an API for examining the warnings
46
emitted so far in a test.
49
def assertDictSubset(self, set, subset):
51
Assert that all the keys present in C{subset} are also present in
52
C{set} and that the corresponding values are equal.
54
for k, v in subset.iteritems():
55
self.assertEqual(set[k], v)
58
def assertDictSubsets(self, sets, subsets):
60
For each pair of corresponding elements in C{sets} and C{subsets},
61
assert that the element from C{subsets} is a subset of the element from
64
self.assertEqual(len(sets), len(subsets))
65
for a, b in zip(sets, subsets):
66
self.assertDictSubset(a, b)
71
If no warnings are emitted by a test, L{TestCase.flushWarnings} returns
74
self.assertEqual(self.flushWarnings(), [])
77
def test_several(self):
79
If several warnings are emitted by a test, L{TestCase.flushWarnings}
80
returns a list containing all of them.
82
firstMessage = "first warning message"
83
firstCategory = UserWarning
84
warnings.warn(message=firstMessage, category=firstCategory)
86
secondMessage = "second warning message"
87
secondCategory = RuntimeWarning
88
warnings.warn(message=secondMessage, category=secondCategory)
90
self.assertDictSubsets(
92
[{'category': firstCategory, 'message': firstMessage},
93
{'category': secondCategory, 'message': secondMessage}])
96
def test_repeated(self):
98
The same warning triggered twice from the same place is included twice
99
in the list returned by L{TestCase.flushWarnings}.
101
message = "the message"
102
category = RuntimeWarning
104
warnings.warn(message=message, category=category)
106
self.assertDictSubsets(
107
self.flushWarnings(),
108
[{'category': category, 'message': message}] * 2)
111
def test_cleared(self):
113
After a particular warning event has been returned by
114
L{TestCase.flushWarnings}, it is not returned by subsequent calls.
116
message = "the message"
117
category = RuntimeWarning
118
warnings.warn(message=message, category=category)
119
self.assertDictSubsets(
120
self.flushWarnings(),
121
[{'category': category, 'message': message}])
122
self.assertEqual(self.flushWarnings(), [])
125
def test_unflushed(self):
127
Any warnings emitted by a test which are not flushed are emitted to the
128
Python warning system.
130
result = TestResult()
131
case = Mask.MockTests('test_unflushed')
133
warningsShown = self.flushWarnings([Mask.MockTests.test_unflushed])
134
self.assertEqual(warningsShown[0]['message'], 'some warning text')
135
self.assertIdentical(warningsShown[0]['category'], UserWarning)
137
where = case.test_unflushed.im_func.func_code
138
filename = where.co_filename
139
# If someone edits MockTests.test_unflushed, the value added to
140
# firstlineno might need to change.
141
lineno = where.co_firstlineno + 4
143
self.assertEqual(warningsShown[0]['filename'], filename)
144
self.assertEqual(warningsShown[0]['lineno'], lineno)
146
self.assertEqual(len(warningsShown), 1)
149
def test_flushed(self):
151
Any warnings emitted by a test which are flushed are not emitted to the
152
Python warning system.
154
result = TestResult()
155
case = Mask.MockTests('test_flushed')
157
monkey = self.patch(sys, 'stdout', output)
160
self.assertEqual(output.getvalue(), "")
163
def test_warningsConfiguredAsErrors(self):
165
If a warnings filter has been installed which turns warnings into
166
exceptions, tests have an error added to the reporter for them for each
169
class CustomWarning(Warning):
172
result = TestResult()
173
case = Mask.MockTests('test_unflushed')
174
case.category = CustomWarning
176
originalWarnings = warnings.filters[:]
178
warnings.simplefilter('error')
180
self.assertEqual(len(result.errors), 1)
181
self.assertIdentical(result.errors[0][0], case)
182
result.errors[0][1].trap(CustomWarning)
184
warnings.filters[:] = originalWarnings
187
def test_flushedWarningsConfiguredAsErrors(self):
189
If a warnings filter has been installed which turns warnings into
190
exceptions, tests which emit those warnings but flush them do not have
191
an error added to the reporter.
193
class CustomWarning(Warning):
196
result = TestResult()
197
case = Mask.MockTests('test_flushed')
198
case.category = CustomWarning
200
originalWarnings = warnings.filters[:]
202
warnings.simplefilter('error')
204
self.assertEqual(result.errors, [])
206
warnings.filters[:] = originalWarnings
209
def test_multipleFlushes(self):
211
Any warnings emitted after a call to L{TestCase.flushWarnings} can be
212
flushed by another call to L{TestCase.flushWarnings}.
214
warnings.warn("first message")
215
self.assertEqual(len(self.flushWarnings()), 1)
216
warnings.warn("second message")
217
self.assertEqual(len(self.flushWarnings()), 1)
220
def test_filterOnOffendingFunction(self):
222
The list returned by L{TestCase.flushWarnings} includes only those
223
warnings which refer to the source of the function passed as the value
224
for C{offendingFunction}, if a value is passed for that parameter.
226
firstMessage = "first warning text"
227
firstCategory = UserWarning
229
warnings.warn(firstMessage, firstCategory, stacklevel=1)
231
secondMessage = "some text"
232
secondCategory = RuntimeWarning
234
warnings.warn(secondMessage, secondCategory, stacklevel=1)
239
self.assertDictSubsets(
240
self.flushWarnings(offendingFunctions=[one]),
241
[{'category': firstCategory, 'message': firstMessage}])
242
self.assertDictSubsets(
243
self.flushWarnings(offendingFunctions=[two]),
244
[{'category': secondCategory, 'message': secondMessage}])
247
def test_functionBoundaries(self):
249
Verify that warnings emitted at the very edges of a function are still
250
determined to be emitted from that function.
253
warnings.warn("first line warning")
254
warnings.warn("internal line warning")
255
warnings.warn("last line warning")
259
len(self.flushWarnings(offendingFunctions=[warner])), 3)
262
def test_invalidFilter(self):
264
If an object which is neither a function nor a method is included in
265
the C{offendingFunctions} list, L{TestCase.flushWarnings} raises
266
L{ValueError}. Such a call flushes no warnings.
268
warnings.warn("oh no")
269
self.assertRaises(ValueError, self.flushWarnings, [None])
270
self.assertEqual(len(self.flushWarnings()), 1)
273
def test_missingSource(self):
275
Warnings emitted by a function the source code of which is not
276
available can still be flushed.
278
package = FilePath(self.mktemp()).child('twisted_private_helper')
280
package.child('__init__.py').setContent('')
281
package.child('missingsourcefile.py').setContent('''
284
warnings.warn("oh no")
286
sys.path.insert(0, package.parent().path)
287
self.addCleanup(sys.path.remove, package.parent().path)
288
from twisted_private_helper import missingsourcefile
289
self.addCleanup(sys.modules.pop, 'twisted_private_helper')
290
self.addCleanup(sys.modules.pop, missingsourcefile.__name__)
291
package.child('missingsourcefile.py').remove()
293
missingsourcefile.foo()
294
self.assertEqual(len(self.flushWarnings([missingsourcefile.foo])), 1)
297
def test_renamedSource(self):
299
Warnings emitted by a function defined in a file which has been renamed
300
since it was initially compiled can still be flushed.
302
This is testing the code which specifically supports working around the
303
unfortunate behavior of CPython to write a .py source file name into
304
the .pyc files it generates and then trust that it is correct in
305
various places. If source files are renamed, .pyc files may not be
306
regenerated, but they will contain incorrect filenames.
308
package = FilePath(self.mktemp()).child('twisted_private_helper')
310
package.child('__init__.py').setContent('')
311
package.child('module.py').setContent('''
314
warnings.warn("oh no")
316
sys.path.insert(0, package.parent().path)
317
self.addCleanup(sys.path.remove, package.parent().path)
319
# Import it to cause pycs to be generated
320
from twisted_private_helper import module
322
# Clean up the state resulting from that import; we're not going to use
323
# this module, so it should go away.
324
del sys.modules['twisted_private_helper']
325
del sys.modules[module.__name__]
327
# Rename the source directory
328
package.moveTo(package.sibling('twisted_renamed_helper'))
330
# Import the newly renamed version
331
from twisted_renamed_helper import module
332
self.addCleanup(sys.modules.pop, 'twisted_renamed_helper')
333
self.addCleanup(sys.modules.pop, module.__name__)
335
# Generate the warning
339
self.assertEqual(len(self.flushWarnings([module.foo])), 1)
343
class FakeWarning(Warning):
348
class CollectWarningsTests(TestCase):
350
Tests for L{_collectWarnings}.
352
def test_callsObserver(self):
354
L{_collectWarnings} calls the observer with each emitted warning.
356
firstMessage = "dummy calls observer warning"
357
secondMessage = firstMessage[::-1]
360
events.append('call')
361
warnings.warn(firstMessage)
362
warnings.warn(secondMessage)
363
events.append('returning')
365
_collectWarnings(events.append, f)
367
self.assertEqual(events[0], 'call')
368
self.assertEqual(events[1].message, firstMessage)
369
self.assertEqual(events[2].message, secondMessage)
370
self.assertEqual(events[3], 'returning')
371
self.assertEqual(len(events), 4)
374
def test_suppresses(self):
376
Any warnings emitted by a call to a function passed to
377
L{_collectWarnings} are not actually emitted to the warning system.
380
self.patch(sys, 'stdout', output)
381
_collectWarnings(lambda x: None, warnings.warn, "text")
382
self.assertEqual(output.getvalue(), "")
385
def test_callsFunction(self):
387
L{_collectWarnings} returns the result of calling the callable passed to
388
it with the parameters given.
393
def f(*args, **kwargs):
394
arguments.append((args, kwargs))
397
result = _collectWarnings(lambda x: None, f, 1, 'a', b=2, c='d')
398
self.assertEqual(arguments, [((1, 'a'), {'b': 2, 'c': 'd'})])
399
self.assertIdentical(result, value)
402
def test_duplicateWarningCollected(self):
404
Subsequent emissions of a warning from a particular source site can be
405
collected by L{_collectWarnings}. In particular, the per-module
406
emitted-warning cache should be bypassed (I{__warningregistry__}).
408
# Make sure the worst case is tested: if __warningregistry__ isn't in a
409
# module's globals, then the warning system will add it and start using
410
# it to avoid emitting duplicate warnings. Delete __warningregistry__
411
# to ensure that even modules which are first imported as a test is
412
# running still interact properly with the warning system.
413
global __warningregistry__
414
del __warningregistry__
418
warnings.simplefilter('default')
421
_collectWarnings(events.append, f)
422
self.assertEqual(len(events), 1)
423
self.assertEqual(events[0].message, "foo")
424
self.assertEqual(len(self.flushWarnings()), 1)
427
def test_immutableObject(self):
429
L{_collectWarnings}'s behavior is not altered by the presence of an
430
object which cannot have attributes set on it as a value in
434
sys.modules[key] = key
435
self.addCleanup(sys.modules.pop, key)
436
self.test_duplicateWarningCollected()