12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Tests for Twisted's deprecation framework, L{twisted.python.deprecate}.
- """
- from __future__ import division, absolute_import
- import sys, types, warnings, inspect
- from os.path import normcase
- from warnings import simplefilter, catch_warnings
- try:
- from importlib import invalidate_caches
- except ImportError:
- invalidate_caches = None
- from twisted.python import deprecate
- from twisted.python.deprecate import _getDeprecationWarningString
- from twisted.python.deprecate import DEPRECATION_WARNING_FORMAT
- from twisted.python.deprecate import (
- getDeprecationWarningString,
- deprecated, _appendToDocstring, _getDeprecationDocstring,
- _fullyQualifiedName as fullyQualifiedName,
- _mutuallyExclusiveArguments,
- deprecatedProperty,
- _passedArgSpec, _passedSignature
- )
- from twisted.python.compat import _PY3, execfile
- from incremental import Version
- from twisted.python.runtime import platform
- from twisted.python.filepath import FilePath
- from twisted.python.test import deprecatedattributes
- from twisted.python.test.modules_helpers import TwistedModulesMixin
- from twisted.trial.unittest import SynchronousTestCase
- # Note that various tests in this module require manual encoding of paths to
- # utf-8. This can be fixed once FilePath supports Unicode; see #2366, #4736,
- # #5203.
- class _MockDeprecatedAttribute(object):
- """
- Mock of L{twisted.python.deprecate._DeprecatedAttribute}.
- @ivar value: The value of the attribute.
- """
- def __init__(self, value):
- self.value = value
- def get(self):
- """
- Get a known value.
- """
- return self.value
- class ModuleProxyTests(SynchronousTestCase):
- """
- Tests for L{twisted.python.deprecate._ModuleProxy}, which proxies
- access to module-level attributes, intercepting access to deprecated
- attributes and passing through access to normal attributes.
- """
- def _makeProxy(self, **attrs):
- """
- Create a temporary module proxy object.
- @param **kw: Attributes to initialise on the temporary module object
- @rtype: L{twistd.python.deprecate._ModuleProxy}
- """
- mod = types.ModuleType('foo')
- for key, value in attrs.items():
- setattr(mod, key, value)
- return deprecate._ModuleProxy(mod)
- def test_getattrPassthrough(self):
- """
- Getting a normal attribute on a L{twisted.python.deprecate._ModuleProxy}
- retrieves the underlying attribute's value, and raises C{AttributeError}
- if a non-existent attribute is accessed.
- """
- proxy = self._makeProxy(SOME_ATTRIBUTE='hello')
- self.assertIs(proxy.SOME_ATTRIBUTE, 'hello')
- self.assertRaises(AttributeError, getattr, proxy, 'DOES_NOT_EXIST')
- def test_getattrIntercept(self):
- """
- Getting an attribute marked as being deprecated on
- L{twisted.python.deprecate._ModuleProxy} results in calling the
- deprecated wrapper's C{get} method.
- """
- proxy = self._makeProxy()
- _deprecatedAttributes = object.__getattribute__(
- proxy, '_deprecatedAttributes')
- _deprecatedAttributes['foo'] = _MockDeprecatedAttribute(42)
- self.assertEqual(proxy.foo, 42)
- def test_privateAttributes(self):
- """
- Private attributes of L{twisted.python.deprecate._ModuleProxy} are
- inaccessible when regular attribute access is used.
- """
- proxy = self._makeProxy()
- self.assertRaises(AttributeError, getattr, proxy, '_module')
- self.assertRaises(
- AttributeError, getattr, proxy, '_deprecatedAttributes')
- def test_setattr(self):
- """
- Setting attributes on L{twisted.python.deprecate._ModuleProxy} proxies
- them through to the wrapped module.
- """
- proxy = self._makeProxy()
- proxy._module = 1
- self.assertNotEqual(object.__getattribute__(proxy, '_module'), 1)
- self.assertEqual(proxy._module, 1)
- def test_repr(self):
- """
- L{twisted.python.deprecated._ModuleProxy.__repr__} produces a string
- containing the proxy type and a representation of the wrapped module
- object.
- """
- proxy = self._makeProxy()
- realModule = object.__getattribute__(proxy, '_module')
- self.assertEqual(
- repr(proxy), '<%s module=%r>' % (type(proxy).__name__, realModule))
- class DeprecatedAttributeTests(SynchronousTestCase):
- """
- Tests for L{twisted.python.deprecate._DeprecatedAttribute} and
- L{twisted.python.deprecate.deprecatedModuleAttribute}, which issue
- warnings for deprecated module-level attributes.
- """
- def setUp(self):
- self.version = deprecatedattributes.version
- self.message = deprecatedattributes.message
- self._testModuleName = __name__ + '.foo'
- def _getWarningString(self, attr):
- """
- Create the warning string used by deprecated attributes.
- """
- return _getDeprecationWarningString(
- deprecatedattributes.__name__ + '.' + attr,
- deprecatedattributes.version,
- DEPRECATION_WARNING_FORMAT + ': ' + deprecatedattributes.message)
- def test_deprecatedAttributeHelper(self):
- """
- L{twisted.python.deprecate._DeprecatedAttribute} correctly sets its
- __name__ to match that of the deprecated attribute and emits a warning
- when the original attribute value is accessed.
- """
- name = 'ANOTHER_DEPRECATED_ATTRIBUTE'
- setattr(deprecatedattributes, name, 42)
- attr = deprecate._DeprecatedAttribute(
- deprecatedattributes, name, self.version, self.message)
- self.assertEqual(attr.__name__, name)
- # Since we're accessing the value getter directly, as opposed to via
- # the module proxy, we need to match the warning's stack level.
- def addStackLevel():
- attr.get()
- # Access the deprecated attribute.
- addStackLevel()
- warningsShown = self.flushWarnings([
- self.test_deprecatedAttributeHelper])
- self.assertIs(warningsShown[0]['category'], DeprecationWarning)
- self.assertEqual(
- warningsShown[0]['message'],
- self._getWarningString(name))
- self.assertEqual(len(warningsShown), 1)
- def test_deprecatedAttribute(self):
- """
- L{twisted.python.deprecate.deprecatedModuleAttribute} wraps a
- module-level attribute in an object that emits a deprecation warning
- when it is accessed the first time only, while leaving other unrelated
- attributes alone.
- """
- # Accessing non-deprecated attributes does not issue a warning.
- deprecatedattributes.ANOTHER_ATTRIBUTE
- warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
- self.assertEqual(len(warningsShown), 0)
- name = 'DEPRECATED_ATTRIBUTE'
- # Access the deprecated attribute. This uses getattr to avoid repeating
- # the attribute name.
- getattr(deprecatedattributes, name)
- warningsShown = self.flushWarnings([self.test_deprecatedAttribute])
- self.assertEqual(len(warningsShown), 1)
- self.assertIs(warningsShown[0]['category'], DeprecationWarning)
- self.assertEqual(
- warningsShown[0]['message'],
- self._getWarningString(name))
- def test_wrappedModule(self):
- """
- Deprecating an attribute in a module replaces and wraps that module
- instance, in C{sys.modules}, with a
- L{twisted.python.deprecate._ModuleProxy} instance but only if it hasn't
- already been wrapped.
- """
- sys.modules[self._testModuleName] = mod = types.ModuleType('foo')
- self.addCleanup(sys.modules.pop, self._testModuleName)
- setattr(mod, 'first', 1)
- setattr(mod, 'second', 2)
- deprecate.deprecatedModuleAttribute(
- Version('Twisted', 8, 0, 0),
- 'message',
- self._testModuleName,
- 'first')
- proxy = sys.modules[self._testModuleName]
- self.assertNotEqual(proxy, mod)
- deprecate.deprecatedModuleAttribute(
- Version('Twisted', 8, 0, 0),
- 'message',
- self._testModuleName,
- 'second')
- self.assertIs(proxy, sys.modules[self._testModuleName])
- class ImportedModuleAttributeTests(TwistedModulesMixin, SynchronousTestCase):
- """
- Tests for L{deprecatedModuleAttribute} which involve loading a module via
- 'import'.
- """
- _packageInit = """\
- from twisted.python.deprecate import deprecatedModuleAttribute
- from incremental import Version
- deprecatedModuleAttribute(
- Version('Package', 1, 2, 3), 'message', __name__, 'module')
- """
- def pathEntryTree(self, tree):
- """
- Create some files in a hierarchy, based on a dictionary describing those
- files. The resulting hierarchy will be placed onto sys.path for the
- duration of the test.
- @param tree: A dictionary representing a directory structure. Keys are
- strings, representing filenames, dictionary values represent
- directories, string values represent file contents.
- @return: another dictionary similar to the input, with file content
- strings replaced with L{FilePath} objects pointing at where those
- contents are now stored.
- """
- def makeSomeFiles(pathobj, dirdict):
- pathdict = {}
- for (key, value) in dirdict.items():
- child = pathobj.child(key)
- if isinstance(value, bytes):
- pathdict[key] = child
- child.setContent(value)
- elif isinstance(value, dict):
- child.createDirectory()
- pathdict[key] = makeSomeFiles(child, value)
- else:
- raise ValueError("only strings and dicts allowed as values")
- return pathdict
- base = FilePath(self.mktemp().encode("utf-8"))
- base.makedirs()
- result = makeSomeFiles(base, tree)
- # On Python 3, sys.path cannot include byte paths:
- self.replaceSysPath([base.path.decode("utf-8")] + sys.path)
- self.replaceSysModules(sys.modules.copy())
- return result
- def simpleModuleEntry(self):
- """
- Add a sample module and package to the path, returning a L{FilePath}
- pointing at the module which will be loadable as C{package.module}.
- """
- paths = self.pathEntryTree(
- {b"package": {b"__init__.py": self._packageInit.encode("utf-8"),
- b"module.py": b""}})
- return paths[b'package'][b'module.py']
- def checkOneWarning(self, modulePath):
- """
- Verification logic for L{test_deprecatedModule}.
- """
- from package import module
- self.assertEqual(FilePath(module.__file__.encode("utf-8")),
- modulePath)
- emitted = self.flushWarnings([self.checkOneWarning])
- self.assertEqual(len(emitted), 1)
- self.assertEqual(emitted[0]['message'],
- 'package.module was deprecated in Package 1.2.3: '
- 'message')
- self.assertEqual(emitted[0]['category'], DeprecationWarning)
- def test_deprecatedModule(self):
- """
- If L{deprecatedModuleAttribute} is used to deprecate a module attribute
- of a package, only one deprecation warning is emitted when the
- deprecated module is imported.
- """
- self.checkOneWarning(self.simpleModuleEntry())
- def test_deprecatedModuleMultipleTimes(self):
- """
- If L{deprecatedModuleAttribute} is used to deprecate a module attribute
- of a package, only one deprecation warning is emitted when the
- deprecated module is subsequently imported.
- """
- mp = self.simpleModuleEntry()
- # The first time, the code needs to be loaded.
- self.checkOneWarning(mp)
- # The second time, things are slightly different; the object's already
- # in the namespace.
- self.checkOneWarning(mp)
- # The third and fourth times, things things should all be exactly the
- # same, but this is a sanity check to make sure the implementation isn't
- # special casing the second time. Also, putting these cases into a loop
- # means that the stack will be identical, to make sure that the
- # implementation doesn't rely too much on stack-crawling.
- for x in range(2):
- self.checkOneWarning(mp)
- class WarnAboutFunctionTests(SynchronousTestCase):
- """
- Tests for L{twisted.python.deprecate.warnAboutFunction} which allows the
- callers of a function to issue a C{DeprecationWarning} about that function.
- """
- def setUp(self):
- """
- Create a file that will have known line numbers when emitting warnings.
- """
- self.package = FilePath(self.mktemp().encode("utf-8")
- ).child(b'twisted_private_helper')
- self.package.makedirs()
- self.package.child(b'__init__.py').setContent(b'')
- self.package.child(b'module.py').setContent(b'''
- "A module string"
- from twisted.python import deprecate
- def testFunction():
- "A doc string"
- a = 1 + 2
- return a
- def callTestFunction():
- b = testFunction()
- if b == 3:
- deprecate.warnAboutFunction(testFunction, "A Warning String")
- ''')
- # Python 3 doesn't accept bytes in sys.path:
- packagePath = self.package.parent().path.decode("utf-8")
- sys.path.insert(0, packagePath)
- self.addCleanup(sys.path.remove, packagePath)
- modules = sys.modules.copy()
- self.addCleanup(
- lambda: (sys.modules.clear(), sys.modules.update(modules)))
- # On Windows on Python 3, most FilePath interactions produce
- # DeprecationWarnings, so flush them here so that they don't interfere
- # with the tests.
- if platform.isWindows() and _PY3:
- self.flushWarnings()
- def test_warning(self):
- """
- L{deprecate.warnAboutFunction} emits a warning the file and line number
- of which point to the beginning of the implementation of the function
- passed to it.
- """
- def aFunc():
- pass
- deprecate.warnAboutFunction(aFunc, 'A Warning Message')
- warningsShown = self.flushWarnings()
- filename = __file__
- if filename.lower().endswith('.pyc'):
- filename = filename[:-1]
- self.assertSamePath(
- FilePath(warningsShown[0]["filename"]), FilePath(filename))
- self.assertEqual(warningsShown[0]["message"], "A Warning Message")
- def test_warningLineNumber(self):
- """
- L{deprecate.warnAboutFunction} emits a C{DeprecationWarning} with the
- number of a line within the implementation of the function passed to it.
- """
- from twisted_private_helper import module
- module.callTestFunction()
- warningsShown = self.flushWarnings()
- self.assertSamePath(
- FilePath(warningsShown[0]["filename"].encode("utf-8")),
- self.package.sibling(b'twisted_private_helper').child(b'module.py'))
- # Line number 9 is the last line in the testFunction in the helper
- # module.
- self.assertEqual(warningsShown[0]["lineno"], 9)
- self.assertEqual(warningsShown[0]["message"], "A Warning String")
- self.assertEqual(len(warningsShown), 1)
- def assertSamePath(self, first, second):
- """
- Assert that the two paths are the same, considering case normalization
- appropriate for the current platform.
- @type first: L{FilePath}
- @type second: L{FilePath}
- @raise C{self.failureType}: If the paths are not the same.
- """
- self.assertTrue(
- normcase(first.path) == normcase(second.path),
- "%r != %r" % (first, second))
- def test_renamedFile(self):
- """
- Even if the implementation of a deprecated function is moved around on
- the filesystem, the line number in the warning emitted by
- L{deprecate.warnAboutFunction} points to a line in the implementation of
- the deprecated function.
- """
- from twisted_private_helper import module
- # Clean up the state resulting from that import; we're not going to use
- # this module, so it should go away.
- del sys.modules['twisted_private_helper']
- del sys.modules[module.__name__]
- # Rename the source directory
- self.package.moveTo(self.package.sibling(b'twisted_renamed_helper'))
- # Make sure importlib notices we've changed importable packages:
- if invalidate_caches:
- invalidate_caches()
- # Import the newly renamed version
- from twisted_renamed_helper import module
- self.addCleanup(sys.modules.pop, 'twisted_renamed_helper')
- self.addCleanup(sys.modules.pop, module.__name__)
- module.callTestFunction()
- warningsShown = self.flushWarnings([module.testFunction])
- warnedPath = FilePath(warningsShown[0]["filename"].encode("utf-8"))
- expectedPath = self.package.sibling(
- b'twisted_renamed_helper').child(b'module.py')
- self.assertSamePath(warnedPath, expectedPath)
- self.assertEqual(warningsShown[0]["lineno"], 9)
- self.assertEqual(warningsShown[0]["message"], "A Warning String")
- self.assertEqual(len(warningsShown), 1)
- def test_filteredWarning(self):
- """
- L{deprecate.warnAboutFunction} emits a warning that will be filtered if
- L{warnings.filterwarning} is called with the module name of the
- deprecated function.
- """
- # Clean up anything *else* that might spuriously filter out the warning,
- # such as the "always" simplefilter set up by unittest._collectWarnings.
- # We'll also rely on trial to restore the original filters afterwards.
- del warnings.filters[:]
- warnings.filterwarnings(
- action="ignore", module="twisted_private_helper")
- from twisted_private_helper import module
- module.callTestFunction()
- warningsShown = self.flushWarnings()
- self.assertEqual(len(warningsShown), 0)
- def test_filteredOnceWarning(self):
- """
- L{deprecate.warnAboutFunction} emits a warning that will be filtered
- once if L{warnings.filterwarning} is called with the module name of the
- deprecated function and an action of once.
- """
- # Clean up anything *else* that might spuriously filter out the warning,
- # such as the "always" simplefilter set up by unittest._collectWarnings.
- # We'll also rely on trial to restore the original filters afterwards.
- del warnings.filters[:]
- warnings.filterwarnings(
- action="module", module="twisted_private_helper")
- from twisted_private_helper import module
- module.callTestFunction()
- module.callTestFunction()
- warningsShown = self.flushWarnings()
- self.assertEqual(len(warningsShown), 1)
- message = warningsShown[0]['message']
- category = warningsShown[0]['category']
- filename = warningsShown[0]['filename']
- lineno = warningsShown[0]['lineno']
- msg = warnings.formatwarning(message, category, filename, lineno)
- self.assertTrue(
- msg.endswith("module.py:9: DeprecationWarning: A Warning String\n"
- " return a\n"),
- "Unexpected warning string: %r" % (msg,))
- def dummyCallable():
- """
- Do nothing.
- This is used to test the deprecation decorators.
- """
- def dummyReplacementMethod():
- """
- Do nothing.
- This is used to test the replacement parameter to L{deprecated}.
- """
- class DeprecationWarningsTests(SynchronousTestCase):
- def test_getDeprecationWarningString(self):
- """
- L{getDeprecationWarningString} returns a string that tells us that a
- callable was deprecated at a certain released version of Twisted.
- """
- version = Version('Twisted', 8, 0, 0)
- self.assertEqual(
- getDeprecationWarningString(self.test_getDeprecationWarningString,
- version),
- "%s.DeprecationWarningsTests.test_getDeprecationWarningString "
- "was deprecated in Twisted 8.0.0" % (__name__,))
- def test_getDeprecationWarningStringWithFormat(self):
- """
- L{getDeprecationWarningString} returns a string that tells us that a
- callable was deprecated at a certain released version of Twisted, with
- a message containing additional information about the deprecation.
- """
- version = Version('Twisted', 8, 0, 0)
- format = DEPRECATION_WARNING_FORMAT + ': This is a message'
- self.assertEqual(
- getDeprecationWarningString(self.test_getDeprecationWarningString,
- version, format),
- '%s.DeprecationWarningsTests.test_getDeprecationWarningString was '
- 'deprecated in Twisted 8.0.0: This is a message' % (__name__,))
- def test_deprecateEmitsWarning(self):
- """
- Decorating a callable with L{deprecated} emits a warning.
- """
- version = Version('Twisted', 8, 0, 0)
- dummy = deprecated(version)(dummyCallable)
- def addStackLevel():
- dummy()
- with catch_warnings(record=True) as caught:
- simplefilter("always")
- addStackLevel()
- self.assertEqual(caught[0].category, DeprecationWarning)
- self.assertEqual(str(caught[0].message), getDeprecationWarningString(dummyCallable, version))
- # rstrip in case .pyc/.pyo
- self.assertEqual(caught[0].filename.rstrip('co'), __file__.rstrip('co'))
- def test_deprecatedPreservesName(self):
- """
- The decorated function has the same name as the original.
- """
- version = Version('Twisted', 8, 0, 0)
- dummy = deprecated(version)(dummyCallable)
- self.assertEqual(dummyCallable.__name__, dummy.__name__)
- self.assertEqual(fullyQualifiedName(dummyCallable),
- fullyQualifiedName(dummy))
- def test_getDeprecationDocstring(self):
- """
- L{_getDeprecationDocstring} returns a note about the deprecation to go
- into a docstring.
- """
- version = Version('Twisted', 8, 0, 0)
- self.assertEqual(
- "Deprecated in Twisted 8.0.0.",
- _getDeprecationDocstring(version, ''))
- def test_deprecatedUpdatesDocstring(self):
- """
- The docstring of the deprecated function is appended with information
- about the deprecation.
- """
- def localDummyCallable():
- """
- Do nothing.
- This is used to test the deprecation decorators.
- """
- version = Version('Twisted', 8, 0, 0)
- dummy = deprecated(version)(localDummyCallable)
- _appendToDocstring(
- localDummyCallable,
- _getDeprecationDocstring(version, ''))
- self.assertEqual(localDummyCallable.__doc__, dummy.__doc__)
- def test_versionMetadata(self):
- """
- Deprecating a function adds version information to the decorated
- version of that function.
- """
- version = Version('Twisted', 8, 0, 0)
- dummy = deprecated(version)(dummyCallable)
- self.assertEqual(version, dummy.deprecatedVersion)
- def test_getDeprecationWarningStringReplacement(self):
- """
- L{getDeprecationWarningString} takes an additional replacement parameter
- that can be used to add information to the deprecation. If the
- replacement parameter is a string, it will be interpolated directly into
- the result.
- """
- version = Version('Twisted', 8, 0, 0)
- warningString = getDeprecationWarningString(
- self.test_getDeprecationWarningString, version,
- replacement="something.foobar")
- self.assertEqual(
- warningString,
- "%s was deprecated in Twisted 8.0.0; please use something.foobar "
- "instead" % (
- fullyQualifiedName(self.test_getDeprecationWarningString),))
- def test_getDeprecationWarningStringReplacementWithCallable(self):
- """
- L{getDeprecationWarningString} takes an additional replacement parameter
- that can be used to add information to the deprecation. If the
- replacement parameter is a callable, its fully qualified name will be
- interpolated into the result.
- """
- version = Version('Twisted', 8, 0, 0)
- warningString = getDeprecationWarningString(
- self.test_getDeprecationWarningString, version,
- replacement=dummyReplacementMethod)
- self.assertEqual(
- warningString,
- "%s was deprecated in Twisted 8.0.0; please use "
- "%s.dummyReplacementMethod instead" % (
- fullyQualifiedName(self.test_getDeprecationWarningString),
- __name__))
- @deprecated(Version('Twisted', 1, 2, 3))
- class DeprecatedClass(object):
- """
- Class which is entirely deprecated without having a replacement.
- """
- class ClassWithDeprecatedProperty(object):
- """
- Class with a single deprecated property.
- """
- _someProtectedValue = None
- @deprecatedProperty(Version('Twisted', 1, 2, 3))
- def someProperty(self):
- """
- Getter docstring.
- @return: The property.
- """
- return self._someProtectedValue
- @someProperty.setter
- def someProperty(self, value):
- """
- Setter docstring.
- """
- self._someProtectedValue = value
- class DeprecatedDecoratorTests(SynchronousTestCase):
- """
- Tests for deprecated decorators.
- """
- def assertDocstring(self, target, expected):
- """
- Check that C{target} object has the C{expected} docstring lines.
- @param target: Object which is checked.
- @type target: C{anything}
- @param expected: List of lines, ignoring empty lines or leading or
- trailing spaces.
- @type expected: L{list} or L{str}
- """
- self.assertEqual(
- expected,
- [x.strip() for x in target.__doc__.splitlines() if x.strip()]
- )
- def test_propertyGetter(self):
- """
- When L{deprecatedProperty} is used on a C{property}, accesses raise a
- L{DeprecationWarning} and getter docstring is updated to inform the
- version in which it was deprecated. C{deprecatedVersion} attribute is
- also set to inform the deprecation version.
- """
- obj = ClassWithDeprecatedProperty()
- obj.someProperty
- self.assertDocstring(
- ClassWithDeprecatedProperty.someProperty,
- [
- 'Getter docstring.',
- '@return: The property.',
- 'Deprecated in Twisted 1.2.3.',
- ],
- )
- ClassWithDeprecatedProperty.someProperty.deprecatedVersion = Version(
- 'Twisted', 1, 2, 3)
- message = (
- 'twisted.python.test.test_deprecate.ClassWithDeprecatedProperty.'
- 'someProperty was deprecated in Twisted 1.2.3'
- )
- warnings = self.flushWarnings([self.test_propertyGetter])
- self.assertEqual(1, len(warnings))
- self.assertEqual(DeprecationWarning, warnings[0]['category'])
- self.assertEqual(message, warnings[0]['message'])
- def test_propertySetter(self):
- """
- When L{deprecatedProperty} is used on a C{property}, setter accesses
- raise a L{DeprecationWarning}.
- """
- newValue = object()
- obj = ClassWithDeprecatedProperty()
- obj.someProperty = newValue
- self.assertIs(newValue, obj._someProtectedValue)
- message = (
- 'twisted.python.test.test_deprecate.ClassWithDeprecatedProperty.'
- 'someProperty was deprecated in Twisted 1.2.3'
- )
- warnings = self.flushWarnings([self.test_propertySetter])
- self.assertEqual(1, len(warnings))
- self.assertEqual(DeprecationWarning, warnings[0]['category'])
- self.assertEqual(message, warnings[0]['message'])
- def test_class(self):
- """
- When L{deprecated} is used on a class, instantiations raise a
- L{DeprecationWarning} and class's docstring is updated to inform the
- version in which it was deprecated. C{deprecatedVersion} attribute is
- also set to inform the deprecation version.
- """
- DeprecatedClass()
- self.assertDocstring(
- DeprecatedClass,
- [('Class which is entirely deprecated without having a '
- 'replacement.'),
- 'Deprecated in Twisted 1.2.3.'],
- )
- DeprecatedClass.deprecatedVersion = Version('Twisted', 1, 2, 3)
- message = (
- 'twisted.python.test.test_deprecate.DeprecatedClass '
- 'was deprecated in Twisted 1.2.3'
- )
- warnings = self.flushWarnings([self.test_class])
- self.assertEqual(1, len(warnings))
- self.assertEqual(DeprecationWarning, warnings[0]['category'])
- self.assertEqual(message, warnings[0]['message'])
- def test_deprecatedReplacement(self):
- """
- L{deprecated} takes an additional replacement parameter that can be used
- to indicate the new, non-deprecated method developers should use. If
- the replacement parameter is a string, it will be interpolated directly
- into the warning message.
- """
- version = Version('Twisted', 8, 0, 0)
- dummy = deprecated(version, "something.foobar")(dummyCallable)
- self.assertEqual(dummy.__doc__,
- "\n"
- " Do nothing.\n\n"
- " This is used to test the deprecation decorators.\n\n"
- " Deprecated in Twisted 8.0.0; please use "
- "something.foobar"
- " instead.\n"
- " ")
- def test_deprecatedReplacementWithCallable(self):
- """
- L{deprecated} takes an additional replacement parameter that can be used
- to indicate the new, non-deprecated method developers should use. If
- the replacement parameter is a callable, its fully qualified name will
- be interpolated into the warning message.
- """
- version = Version('Twisted', 8, 0, 0)
- decorator = deprecated(version, replacement=dummyReplacementMethod)
- dummy = decorator(dummyCallable)
- self.assertEqual(dummy.__doc__,
- "\n"
- " Do nothing.\n\n"
- " This is used to test the deprecation decorators.\n\n"
- " Deprecated in Twisted 8.0.0; please use "
- "%s.dummyReplacementMethod instead.\n"
- " " % (__name__,))
- class AppendToDocstringTests(SynchronousTestCase):
- """
- Test the _appendToDocstring function.
- _appendToDocstring is used to add text to a docstring.
- """
- def test_appendToEmptyDocstring(self):
- """
- Appending to an empty docstring simply replaces the docstring.
- """
- def noDocstring():
- pass
- _appendToDocstring(noDocstring, "Appended text.")
- self.assertEqual("Appended text.", noDocstring.__doc__)
- def test_appendToSingleLineDocstring(self):
- """
- Appending to a single line docstring places the message on a new line,
- with a blank line separating it from the rest of the docstring.
- The docstring ends with a newline, conforming to Twisted and PEP 8
- standards. Unfortunately, the indentation is incorrect, since the
- existing docstring doesn't have enough info to help us indent
- properly.
- """
- def singleLineDocstring():
- """This doesn't comply with standards, but is here for a test."""
- _appendToDocstring(singleLineDocstring, "Appended text.")
- self.assertEqual(
- ["This doesn't comply with standards, but is here for a test.",
- "",
- "Appended text."],
- singleLineDocstring.__doc__.splitlines())
- self.assertTrue(singleLineDocstring.__doc__.endswith('\n'))
- def test_appendToMultilineDocstring(self):
- """
- Appending to a multi-line docstring places the messade on a new line,
- with a blank line separating it from the rest of the docstring.
- Because we have multiple lines, we have enough information to do
- indentation.
- """
- def multiLineDocstring():
- """
- This is a multi-line docstring.
- """
- def expectedDocstring():
- """
- This is a multi-line docstring.
- Appended text.
- """
- _appendToDocstring(multiLineDocstring, "Appended text.")
- self.assertEqual(
- expectedDocstring.__doc__, multiLineDocstring.__doc__)
- class MutualArgumentExclusionTests(SynchronousTestCase):
- """
- Tests for L{mutuallyExclusiveArguments}.
- """
- def checkPassed(self, func, *args, **kw):
- """
- Test an invocation of L{passed} with the given function, arguments, and
- keyword arguments.
- @param func: A function whose argspec will be inspected.
- @type func: A callable.
- @param args: The arguments which could be passed to C{func}.
- @param kw: The keyword arguments which could be passed to C{func}.
- @return: L{_passedSignature} or L{_passedArgSpec}'s return value
- @rtype: L{dict}
- """
- if getattr(inspect, "signature", None):
- # Python 3
- return _passedSignature(inspect.signature(func), args, kw)
- else:
- # Python 2
- return _passedArgSpec(inspect.getargspec(func), args, kw)
- def test_passed_simplePositional(self):
- """
- L{passed} identifies the arguments passed by a simple
- positional test.
- """
- def func(a, b):
- pass
- self.assertEqual(self.checkPassed(func, 1, 2), dict(a=1, b=2))
- def test_passed_tooManyArgs(self):
- """
- L{passed} raises a L{TypeError} if too many arguments are
- passed.
- """
- def func(a, b):
- pass
- self.assertRaises(TypeError, self.checkPassed, func, 1, 2, 3)
- def test_passed_doublePassKeyword(self):
- """
- L{passed} raises a L{TypeError} if a argument is passed both
- positionally and by keyword.
- """
- def func(a):
- pass
- self.assertRaises(TypeError, self.checkPassed, func, 1, a=2)
- def test_passed_unspecifiedKeyword(self):
- """
- L{passed} raises a L{TypeError} if a keyword argument not
- present in the function's declaration is passed.
- """
- def func(a):
- pass
- self.assertRaises(TypeError, self.checkPassed, func, 1, z=2)
- def test_passed_star(self):
- """
- L{passed} places additional positional arguments into a tuple
- under the name of the star argument.
- """
- def func(a, *b):
- pass
- self.assertEqual(self.checkPassed(func, 1, 2, 3),
- dict(a=1, b=(2, 3)))
- def test_passed_starStar(self):
- """
- Additional keyword arguments are passed as a dict to the star star
- keyword argument.
- """
- def func(a, **b):
- pass
- self.assertEqual(self.checkPassed(func, 1, x=2, y=3, z=4),
- dict(a=1, b=dict(x=2, y=3, z=4)))
- def test_passed_noDefaultValues(self):
- """
- The results of L{passed} only include arguments explicitly
- passed, not default values.
- """
- def func(a, b, c=1, d=2, e=3):
- pass
- self.assertEqual(self.checkPassed(func, 1, 2, e=7),
- dict(a=1, b=2, e=7))
- def test_mutualExclusionPrimeDirective(self):
- """
- L{mutuallyExclusiveArguments} does not interfere in its
- decoratee's operation, either its receipt of arguments or its return
- value.
- """
- @_mutuallyExclusiveArguments([('a', 'b')])
- def func(x, y, a=3, b=4):
- return x + y + a + b
- self.assertEqual(func(1, 2), 10)
- self.assertEqual(func(1, 2, 7), 14)
- self.assertEqual(func(1, 2, b=7), 13)
- def test_mutualExclusionExcludesByKeyword(self):
- """
- L{mutuallyExclusiveArguments} raises a L{TypeError}n if its
- decoratee is passed a pair of mutually exclusive arguments.
- """
- @_mutuallyExclusiveArguments([['a', 'b']])
- def func(a=3, b=4):
- return a + b
- self.assertRaises(TypeError, func, a=3, b=4)
- def test_invalidParameterType(self):
- """
- Create a fake signature with an invalid parameter
- type to test error handling. The valid parameter
- types are specified in L{inspect.Parameter}.
- """
- class FakeSignature:
- def __init__(self, parameters):
- self.parameters = parameters
- class FakeParameter:
- def __init__(self, name, kind):
- self.name = name
- self.kind = kind
- def func(a, b):
- pass
- func(1, 2)
- parameters = inspect.signature(func).parameters
- dummyParameters = parameters.copy()
- dummyParameters['c'] = FakeParameter("fake", "fake")
- fakeSig = FakeSignature(dummyParameters)
- self.assertRaises(TypeError, _passedSignature, fakeSig, (1, 2), {})
- if not getattr(inspect, "signature", None):
- test_invalidParameterType.skip = "inspect.signature() not available"
- if sys.version_info >= (3,):
- _path = FilePath(__file__).parent().child("_deprecatetests.py.3only")
- _g = {}
- execfile(_path.path, _g)
- KeywordOnlyTests = _g["KeywordOnlyTests"]
- else:
- from twisted.trial.unittest import TestCase
- class KeywordOnlyTests(TestCase):
- """
- A dummy class to show that this test file was discovered but the tests
- are unable to be ran in this version of Python.
- """
- skip = (
- "keyword only arguments (PEP 3102) are "
- "only in Python 3 and higher")
- def test_notAvailable(self):
- """
- A skipped test to show that this was not ran because the Python is
- too old.
- """
|