Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / test / test_deprecate.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Tests for Twisted's deprecation framework, L{twisted.python.deprecate}.
6 """
7
8 import sys, types
9 import warnings
10 from os.path import normcase
11
12 from twisted.trial.unittest import TestCase
13
14 from twisted.python import deprecate
15 from twisted.python.deprecate import _appendToDocstring
16 from twisted.python.deprecate import _getDeprecationDocstring
17 from twisted.python.deprecate import deprecated, getDeprecationWarningString
18 from twisted.python.deprecate import _getDeprecationWarningString
19 from twisted.python.deprecate import DEPRECATION_WARNING_FORMAT
20 from twisted.python.reflect import fullyQualifiedName
21 from twisted.python.versions import Version
22 from twisted.python.filepath import FilePath
23
24 from twisted.python.test import deprecatedattributes
25 from twisted.python.test.modules_helpers import TwistedModulesTestCase
26
27
28
29 def dummyCallable():
30     """
31     Do nothing.
32
33     This is used to test the deprecation decorators.
34     """
35
36
37 def dummyReplacementMethod():
38     """
39     Do nothing.
40
41     This is used to test the replacement parameter to L{deprecated}.
42     """
43
44
45
46 class TestDeprecationWarnings(TestCase):
47     def test_getDeprecationWarningString(self):
48         """
49         L{getDeprecationWarningString} returns a string that tells us that a
50         callable was deprecated at a certain released version of Twisted.
51         """
52         version = Version('Twisted', 8, 0, 0)
53         self.assertEqual(
54             getDeprecationWarningString(self.test_getDeprecationWarningString,
55                                         version),
56             "twisted.python.test.test_deprecate.TestDeprecationWarnings."
57             "test_getDeprecationWarningString was deprecated in "
58             "Twisted 8.0.0")
59
60
61     def test_getDeprecationWarningStringWithFormat(self):
62         """
63         L{getDeprecationWarningString} returns a string that tells us that a
64         callable was deprecated at a certain released version of Twisted, with
65         a message containing additional information about the deprecation.
66         """
67         version = Version('Twisted', 8, 0, 0)
68         format = deprecate.DEPRECATION_WARNING_FORMAT + ': This is a message'
69         self.assertEqual(
70             getDeprecationWarningString(self.test_getDeprecationWarningString,
71                                         version, format),
72             'twisted.python.test.test_deprecate.TestDeprecationWarnings.'
73             'test_getDeprecationWarningString was deprecated in '
74             'Twisted 8.0.0: This is a message')
75
76
77     def test_deprecateEmitsWarning(self):
78         """
79         Decorating a callable with L{deprecated} emits a warning.
80         """
81         version = Version('Twisted', 8, 0, 0)
82         dummy = deprecated(version)(dummyCallable)
83         def addStackLevel():
84             dummy()
85         self.assertWarns(
86             DeprecationWarning,
87             getDeprecationWarningString(dummyCallable, version),
88             __file__,
89             addStackLevel)
90
91
92     def test_deprecatedPreservesName(self):
93         """
94         The decorated function has the same name as the original.
95         """
96         version = Version('Twisted', 8, 0, 0)
97         dummy = deprecated(version)(dummyCallable)
98         self.assertEqual(dummyCallable.__name__, dummy.__name__)
99         self.assertEqual(fullyQualifiedName(dummyCallable),
100                          fullyQualifiedName(dummy))
101
102
103     def test_getDeprecationDocstring(self):
104         """
105         L{_getDeprecationDocstring} returns a note about the deprecation to go
106         into a docstring.
107         """
108         version = Version('Twisted', 8, 0, 0)
109         self.assertEqual(
110             "Deprecated in Twisted 8.0.0.",
111             _getDeprecationDocstring(version, ''))
112
113
114     def test_deprecatedUpdatesDocstring(self):
115         """
116         The docstring of the deprecated function is appended with information
117         about the deprecation.
118         """
119
120         version = Version('Twisted', 8, 0, 0)
121         dummy = deprecated(version)(dummyCallable)
122
123         _appendToDocstring(
124             dummyCallable,
125             _getDeprecationDocstring(version, ''))
126
127         self.assertEqual(dummyCallable.__doc__, dummy.__doc__)
128
129
130     def test_versionMetadata(self):
131         """
132         Deprecating a function adds version information to the decorated
133         version of that function.
134         """
135         version = Version('Twisted', 8, 0, 0)
136         dummy = deprecated(version)(dummyCallable)
137         self.assertEqual(version, dummy.deprecatedVersion)
138
139
140     def test_getDeprecationWarningStringReplacement(self):
141         """
142         L{getDeprecationWarningString} takes an additional replacement parameter
143         that can be used to add information to the deprecation.  If the
144         replacement parameter is a string, it will be interpolated directly into
145         the result.
146         """
147         version = Version('Twisted', 8, 0, 0)
148         warningString = getDeprecationWarningString(
149             self.test_getDeprecationWarningString, version,
150             replacement="something.foobar")
151         self.assertEqual(
152             warningString,
153             "%s was deprecated in Twisted 8.0.0; please use something.foobar "
154             "instead" % (
155                 fullyQualifiedName(self.test_getDeprecationWarningString),))
156
157
158     def test_getDeprecationWarningStringReplacementWithCallable(self):
159         """
160         L{getDeprecationWarningString} takes an additional replacement parameter
161         that can be used to add information to the deprecation. If the
162         replacement parameter is a callable, its fully qualified name will be
163         interpolated into the result.
164         """
165         version = Version('Twisted', 8, 0, 0)
166         warningString = getDeprecationWarningString(
167             self.test_getDeprecationWarningString, version,
168             replacement=dummyReplacementMethod)
169         self.assertEqual(
170             warningString,
171             "%s was deprecated in Twisted 8.0.0; please use "
172             "twisted.python.test.test_deprecate.dummyReplacementMethod "
173             "instead" % (
174                 fullyQualifiedName(self.test_getDeprecationWarningString),))
175
176
177     def test_deprecatedReplacement(self):
178         """
179         L{deprecated} takes an additional replacement parameter that can be used
180         to indicate the new, non-deprecated method developers should use.  If
181         the replacement parameter is a string, it will be interpolated directly
182         into the warning message.
183         """
184         version = Version('Twisted', 8, 0, 0)
185         dummy = deprecated(version, "something.foobar")(dummyCallable)
186         self.assertEqual(dummy.__doc__,
187             "\n"
188             "    Do nothing.\n\n"
189             "    This is used to test the deprecation decorators.\n\n"
190             "    Deprecated in Twisted 8.0.0; please use "
191             "something.foobar"
192             " instead.\n"
193             "    ")
194
195
196     def test_deprecatedReplacementWithCallable(self):
197         """
198         L{deprecated} takes an additional replacement parameter that can be used
199         to indicate the new, non-deprecated method developers should use.  If
200         the replacement parameter is a callable, its fully qualified name will
201         be interpolated into the warning message.
202         """
203         version = Version('Twisted', 8, 0, 0)
204         decorator = deprecated(version, replacement=dummyReplacementMethod)
205         dummy = decorator(dummyCallable)
206         self.assertEqual(dummy.__doc__,
207             "\n"
208             "    Do nothing.\n\n"
209             "    This is used to test the deprecation decorators.\n\n"
210             "    Deprecated in Twisted 8.0.0; please use "
211             "twisted.python.test.test_deprecate.dummyReplacementMethod"
212             " instead.\n"
213             "    ")
214
215
216
217 class TestAppendToDocstring(TestCase):
218     """
219     Test the _appendToDocstring function.
220
221     _appendToDocstring is used to add text to a docstring.
222     """
223
224     def test_appendToEmptyDocstring(self):
225         """
226         Appending to an empty docstring simply replaces the docstring.
227         """
228
229         def noDocstring():
230             pass
231
232         _appendToDocstring(noDocstring, "Appended text.")
233         self.assertEqual("Appended text.", noDocstring.__doc__)
234
235
236     def test_appendToSingleLineDocstring(self):
237         """
238         Appending to a single line docstring places the message on a new line,
239         with a blank line separating it from the rest of the docstring.
240
241         The docstring ends with a newline, conforming to Twisted and PEP 8
242         standards. Unfortunately, the indentation is incorrect, since the
243         existing docstring doesn't have enough info to help us indent
244         properly.
245         """
246
247         def singleLineDocstring():
248             """This doesn't comply with standards, but is here for a test."""
249
250         _appendToDocstring(singleLineDocstring, "Appended text.")
251         self.assertEqual(
252             ["This doesn't comply with standards, but is here for a test.",
253              "",
254              "Appended text."],
255             singleLineDocstring.__doc__.splitlines())
256         self.assertTrue(singleLineDocstring.__doc__.endswith('\n'))
257
258
259     def test_appendToMultilineDocstring(self):
260         """
261         Appending to a multi-line docstring places the messade on a new line,
262         with a blank line separating it from the rest of the docstring.
263
264         Because we have multiple lines, we have enough information to do
265         indentation.
266         """
267
268         def multiLineDocstring():
269             """
270             This is a multi-line docstring.
271             """
272
273         def expectedDocstring():
274             """
275             This is a multi-line docstring.
276
277             Appended text.
278             """
279
280         _appendToDocstring(multiLineDocstring, "Appended text.")
281         self.assertEqual(
282             expectedDocstring.__doc__, multiLineDocstring.__doc__)
283
284
285
286 class _MockDeprecatedAttribute(object):
287     """
288     Mock of L{twisted.python.deprecate._DeprecatedAttribute}.
289
290     @ivar value: The value of the attribute.
291     """
292     def __init__(self, value):
293         self.value = value
294
295
296     def get(self):
297         """
298         Get a known value.
299         """
300         return self.value
301
302
303
304 class ModuleProxyTests(TestCase):
305     """
306     Tests for L{twisted.python.deprecate._ModuleProxy}, which proxies
307     access to module-level attributes, intercepting access to deprecated
308     attributes and passing through access to normal attributes.
309     """
310     def _makeProxy(self, **attrs):
311         """
312         Create a temporary module proxy object.
313
314         @param **kw: Attributes to initialise on the temporary module object
315
316         @rtype: L{twistd.python.deprecate._ModuleProxy}
317         """
318         mod = types.ModuleType('foo')
319         for key, value in attrs.iteritems():
320             setattr(mod, key, value)
321         return deprecate._ModuleProxy(mod)
322
323
324     def test_getattrPassthrough(self):
325         """
326         Getting a normal attribute on a L{twisted.python.deprecate._ModuleProxy}
327         retrieves the underlying attribute's value, and raises C{AttributeError}
328         if a non-existant attribute is accessed.
329         """
330         proxy = self._makeProxy(SOME_ATTRIBUTE='hello')
331         self.assertIdentical(proxy.SOME_ATTRIBUTE, 'hello')
332         self.assertRaises(AttributeError, getattr, proxy, 'DOES_NOT_EXIST')
333
334
335     def test_getattrIntercept(self):
336         """
337         Getting an attribute marked as being deprecated on
338         L{twisted.python.deprecate._ModuleProxy} results in calling the
339         deprecated wrapper's C{get} method.
340         """
341         proxy = self._makeProxy()
342         _deprecatedAttributes = object.__getattribute__(
343             proxy, '_deprecatedAttributes')
344         _deprecatedAttributes['foo'] = _MockDeprecatedAttribute(42)
345         self.assertEqual(proxy.foo, 42)
346
347
348     def test_privateAttributes(self):
349         """
350         Private attributes of L{twisted.python.deprecate._ModuleProxy} are
351         inaccessible when regular attribute access is used.
352         """
353         proxy = self._makeProxy()
354         self.assertRaises(AttributeError, getattr, proxy, '_module')
355         self.assertRaises(
356             AttributeError, getattr, proxy, '_deprecatedAttributes')
357
358
359     def test_setattr(self):
360         """
361         Setting attributes on L{twisted.python.deprecate._ModuleProxy} proxies
362         them through to the wrapped module.
363         """
364         proxy = self._makeProxy()
365         proxy._module = 1
366         self.assertNotEquals(object.__getattribute__(proxy, '_module'), 1)
367         self.assertEqual(proxy._module, 1)
368
369
370     def test_repr(self):
371         """
372         L{twisted.python.deprecated._ModuleProxy.__repr__} produces a string
373         containing the proxy type and a representation of the wrapped module
374         object.
375         """
376         proxy = self._makeProxy()
377         realModule = object.__getattribute__(proxy, '_module')
378         self.assertEqual(
379             repr(proxy), '<%s module=%r>' % (type(proxy).__name__, realModule))
380
381
382
383 class DeprecatedAttributeTests(TestCase):
384     """
385     Tests for L{twisted.python.deprecate._DeprecatedAttribute} and
386     L{twisted.python.deprecate.deprecatedModuleAttribute}, which issue
387     warnings for deprecated module-level attributes.
388     """
389     def setUp(self):
390         self.version = deprecatedattributes.version
391         self.message = deprecatedattributes.message
392         self._testModuleName = __name__ + '.foo'
393
394
395     def _getWarningString(self, attr):
396         """
397         Create the warning string used by deprecated attributes.
398         """
399         return _getDeprecationWarningString(
400             deprecatedattributes.__name__ + '.' + attr,
401             deprecatedattributes.version,
402             DEPRECATION_WARNING_FORMAT + ': ' + deprecatedattributes.message)
403
404
405     def test_deprecatedAttributeHelper(self):
406         """
407         L{twisted.python.deprecate._DeprecatedAttribute} correctly sets its
408         __name__ to match that of the deprecated attribute and emits a warning
409         when the original attribute value is accessed.
410         """
411         name = 'ANOTHER_DEPRECATED_ATTRIBUTE'
412         setattr(deprecatedattributes, name, 42)
413         attr = deprecate._DeprecatedAttribute(
414             deprecatedattributes, name, self.version, self.message)
415
416         self.assertEqual(attr.__name__, name)
417
418         # Since we're accessing the value getter directly, as opposed to via
419         # the module proxy, we need to match the warning's stack level.
420         def addStackLevel():
421             attr.get()
422
423         # Access the deprecated attribute.
424         addStackLevel()
425         warningsShown = self.flushWarnings([
426             self.test_deprecatedAttributeHelper])
427         self.assertIdentical(warningsShown[0]['category'], DeprecationWarning)
428         self.assertEqual(
429             warningsShown[0]['message'],
430             self._getWarningString(name))
431         self.assertEqual(len(warningsShown), 1)
432
433
434     def test_deprecatedAttribute(self):
435         """
436         L{twisted.python.deprecate.deprecatedModuleAttribute} wraps a
437         module-level attribute in an object that emits a deprecation warning
438         when it is accessed the first time only, while leaving other unrelated
439         attributes alone.
440         """
441         # Accessing non-deprecated attributes does not issue a warning.
442         deprecatedattributes.ANOTHER_ATTRIBUTE
443         warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
444         self.assertEqual(len(warningsShown), 0)
445
446         name = 'DEPRECATED_ATTRIBUTE'
447
448         # Access the deprecated attribute. This uses getattr to avoid repeating
449         # the attribute name.
450         getattr(deprecatedattributes, name)
451
452         warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
453         self.assertEqual(len(warningsShown), 1)
454         self.assertIdentical(warningsShown[0]['category'], DeprecationWarning)
455         self.assertEqual(
456             warningsShown[0]['message'],
457             self._getWarningString(name))
458
459
460     def test_wrappedModule(self):
461         """
462         Deprecating an attribute in a module replaces and wraps that module
463         instance, in C{sys.modules}, with a
464         L{twisted.python.deprecate._ModuleProxy} instance but only if it hasn't
465         already been wrapped.
466         """
467         sys.modules[self._testModuleName] = mod = types.ModuleType('foo')
468         self.addCleanup(sys.modules.pop, self._testModuleName)
469
470         setattr(mod, 'first', 1)
471         setattr(mod, 'second', 2)
472
473         deprecate.deprecatedModuleAttribute(
474             Version('Twisted', 8, 0, 0),
475             'message',
476             self._testModuleName,
477             'first')
478
479         proxy = sys.modules[self._testModuleName]
480         self.assertNotEqual(proxy, mod)
481
482         deprecate.deprecatedModuleAttribute(
483             Version('Twisted', 8, 0, 0),
484             'message',
485             self._testModuleName,
486             'second')
487
488         self.assertIdentical(proxy, sys.modules[self._testModuleName])
489
490
491
492 class ImportedModuleAttributeTests(TwistedModulesTestCase):
493     """
494     Tests for L{deprecatedModuleAttribute} which involve loading a module via
495     'import'.
496     """
497
498     _packageInit = """\
499 from twisted.python.deprecate import deprecatedModuleAttribute
500 from twisted.python.versions import Version
501
502 deprecatedModuleAttribute(
503     Version('Package', 1, 2, 3), 'message', __name__, 'module')
504 """
505
506
507     def pathEntryTree(self, tree):
508         """
509         Create some files in a hierarchy, based on a dictionary describing those
510         files.  The resulting hierarchy will be placed onto sys.path for the
511         duration of the test.
512
513         @param tree: A dictionary representing a directory structure.  Keys are
514             strings, representing filenames, dictionary values represent
515             directories, string values represent file contents.
516
517         @return: another dictionary similar to the input, with file content
518             strings replaced with L{FilePath} objects pointing at where those
519             contents are now stored.
520         """
521         def makeSomeFiles(pathobj, dirdict):
522             pathdict = {}
523             for (key, value) in dirdict.items():
524                 child = pathobj.child(key)
525                 if isinstance(value, str):
526                     pathdict[key] = child
527                     child.setContent(value)
528                 elif isinstance(value, dict):
529                     child.createDirectory()
530                     pathdict[key] = makeSomeFiles(child, value)
531                 else:
532                     raise ValueError("only strings and dicts allowed as values")
533             return pathdict
534         base = FilePath(self.mktemp())
535         base.makedirs()
536
537         result = makeSomeFiles(base, tree)
538         self.replaceSysPath([base.path] + sys.path)
539         self.replaceSysModules(sys.modules.copy())
540         return result
541
542
543     def simpleModuleEntry(self):
544         """
545         Add a sample module and package to the path, returning a L{FilePath}
546         pointing at the module which will be loadable as C{package.module}.
547         """
548         paths = self.pathEntryTree(
549             {"package": {"__init__.py": self._packageInit,
550                          "module.py": ""}})
551         return paths['package']['module.py']
552
553
554     def checkOneWarning(self, modulePath):
555         """
556         Verification logic for L{test_deprecatedModule}.
557         """
558         # import package.module
559         from package import module
560         self.assertEqual(module.__file__, modulePath.path)
561         emitted = self.flushWarnings([self.checkOneWarning])
562         self.assertEqual(len(emitted), 1)
563         self.assertEqual(emitted[0]['message'],
564                           'package.module was deprecated in Package 1.2.3: '
565                           'message')
566         self.assertEqual(emitted[0]['category'], DeprecationWarning)
567
568
569     def test_deprecatedModule(self):
570         """
571         If L{deprecatedModuleAttribute} is used to deprecate a module attribute
572         of a package, only one deprecation warning is emitted when the
573         deprecated module is imported.
574         """
575         self.checkOneWarning(self.simpleModuleEntry())
576
577
578     def test_deprecatedModuleMultipleTimes(self):
579         """
580         If L{deprecatedModuleAttribute} is used to deprecate a module attribute
581         of a package, only one deprecation warning is emitted when the
582         deprecated module is subsequently imported.
583         """
584         mp = self.simpleModuleEntry()
585         # The first time, the code needs to be loaded.
586         self.checkOneWarning(mp)
587         # The second time, things are slightly different; the object's already
588         # in the namespace.
589         self.checkOneWarning(mp)
590         # The third and fourth times, things things should all be exactly the
591         # same, but this is a sanity check to make sure the implementation isn't
592         # special casing the second time.  Also, putting these cases into a loop
593         # means that the stack will be identical, to make sure that the
594         # implementation doesn't rely too much on stack-crawling.
595         for x in range(2):
596             self.checkOneWarning(mp)
597
598
599
600 class WarnAboutFunctionTests(TestCase):
601     """
602     Tests for L{twisted.python.deprecate.warnAboutFunction} which allows the
603     callers of a function to issue a C{DeprecationWarning} about that function.
604     """
605     def setUp(self):
606         """
607         Create a file that will have known line numbers when emitting warnings.
608         """
609         self.package = FilePath(self.mktemp()).child('twisted_private_helper')
610         self.package.makedirs()
611         self.package.child('__init__.py').setContent('')
612         self.package.child('module.py').setContent('''
613 "A module string"
614
615 from twisted.python import deprecate
616
617 def testFunction():
618     "A doc string"
619     a = 1 + 2
620     return a
621
622 def callTestFunction():
623     b = testFunction()
624     if b == 3:
625         deprecate.warnAboutFunction(testFunction, "A Warning String")
626 ''')
627         sys.path.insert(0, self.package.parent().path)
628         self.addCleanup(sys.path.remove, self.package.parent().path)
629
630         modules = sys.modules.copy()
631         self.addCleanup(
632             lambda: (sys.modules.clear(), sys.modules.update(modules)))
633
634
635     def test_warning(self):
636         """
637         L{deprecate.warnAboutFunction} emits a warning the file and line number
638         of which point to the beginning of the implementation of the function
639         passed to it.
640         """
641         def aFunc():
642             pass
643         deprecate.warnAboutFunction(aFunc, 'A Warning Message')
644         warningsShown = self.flushWarnings()
645         filename = __file__
646         if filename.lower().endswith('.pyc'):
647             filename = filename[:-1]
648         self.assertSamePath(
649             FilePath(warningsShown[0]["filename"]), FilePath(filename))
650         self.assertEqual(warningsShown[0]["message"], "A Warning Message")
651
652
653     def test_warningLineNumber(self):
654         """
655         L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
656         number of a line within the implementation of the function passed to it.
657         """
658         from twisted_private_helper import module
659         module.callTestFunction()
660         warningsShown = self.flushWarnings()
661         self.assertSamePath(
662             FilePath(warningsShown[0]["filename"]),
663             self.package.sibling('twisted_private_helper').child('module.py'))
664         # Line number 9 is the last line in the testFunction in the helper
665         # module.
666         self.assertEqual(warningsShown[0]["lineno"], 9)
667         self.assertEqual(warningsShown[0]["message"], "A Warning String")
668         self.assertEqual(len(warningsShown), 1)
669
670
671     def assertSamePath(self, first, second):
672         """
673         Assert that the two paths are the same, considering case normalization
674         appropriate for the current platform.
675
676         @type first: L{FilePath}
677         @type second: L{FilePath}
678
679         @raise C{self.failureType}: If the paths are not the same.
680         """
681         self.assertTrue(
682             normcase(first.path) == normcase(second.path),
683             "%r != %r" % (first, second))
684
685
686     def test_renamedFile(self):
687         """
688         Even if the implementation of a deprecated function is moved around on
689         the filesystem, the line number in the warning emitted by
690         L{deprecate.warnAboutFunction} points to a line in the implementation of
691         the deprecated function.
692         """
693         from twisted_private_helper import module
694         # Clean up the state resulting from that import; we're not going to use
695         # this module, so it should go away.
696         del sys.modules['twisted_private_helper']
697         del sys.modules[module.__name__]
698
699         # Rename the source directory
700         self.package.moveTo(self.package.sibling('twisted_renamed_helper'))
701
702         # Import the newly renamed version
703         from twisted_renamed_helper import module
704         self.addCleanup(sys.modules.pop, 'twisted_renamed_helper')
705         self.addCleanup(sys.modules.pop, module.__name__)
706
707         module.callTestFunction()
708         warningsShown = self.flushWarnings()
709         warnedPath = FilePath(warningsShown[0]["filename"])
710         expectedPath = self.package.sibling(
711             'twisted_renamed_helper').child('module.py')
712         self.assertSamePath(warnedPath, expectedPath)
713         self.assertEqual(warningsShown[0]["lineno"], 9)
714         self.assertEqual(warningsShown[0]["message"], "A Warning String")
715         self.assertEqual(len(warningsShown), 1)
716
717
718     def test_filteredWarning(self):
719         """
720         L{deprecate.warnAboutFunction} emits a warning that will be filtered if
721         L{warnings.filterwarning} is called with the module name of the
722         deprecated function.
723         """
724         # Clean up anything *else* that might spuriously filter out the warning,
725         # such as the "always" simplefilter set up by unittest._collectWarnings.
726         # We'll also rely on trial to restore the original filters afterwards.
727         del warnings.filters[:]
728
729         warnings.filterwarnings(
730             action="ignore", module="twisted_private_helper")
731
732         from twisted_private_helper import module
733         module.callTestFunction()
734
735         warningsShown = self.flushWarnings()
736         self.assertEqual(len(warningsShown), 0)
737
738
739     def test_filteredOnceWarning(self):
740         """
741         L{deprecate.warnAboutFunction} emits a warning that will be filtered
742         once if L{warnings.filterwarning} is called with the module name of the
743         deprecated function and an action of once.
744         """
745         # Clean up anything *else* that might spuriously filter out the warning,
746         # such as the "always" simplefilter set up by unittest._collectWarnings.
747         # We'll also rely on trial to restore the original filters afterwards.
748         del warnings.filters[:]
749
750         warnings.filterwarnings(
751             action="module", module="twisted_private_helper")
752
753         from twisted_private_helper import module
754         module.callTestFunction()
755         module.callTestFunction()
756
757         warningsShown = self.flushWarnings()
758         self.assertEqual(len(warningsShown), 1)
759         message = warningsShown[0]['message']
760         category = warningsShown[0]['category']
761         filename = warningsShown[0]['filename']
762         lineno = warningsShown[0]['lineno']
763         msg = warnings.formatwarning(message, category, filename, lineno)
764         self.assertTrue(
765             msg.endswith("module.py:9: DeprecationWarning: A Warning String\n"
766                          "  return a\n"),
767             "Unexpected warning string: %r" % (msg,))