123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- import operator
- import os
- import shutil
- import sys
- import textwrap
- import tempfile
- from unittest import skipIf, TestCase
-
- import six
-
-
- def isTwistedInstalled():
- try:
- __import__('twisted')
- except ImportError:
- return False
- else:
- return True
-
-
- class _WritesPythonModules(TestCase):
- """
- A helper that enables generating Python module test fixtures.
- """
-
- def setUp(self):
- super(_WritesPythonModules, self).setUp()
-
- from twisted.python.modules import getModule, PythonPath
- from twisted.python.filepath import FilePath
-
- self.getModule = getModule
- self.PythonPath = PythonPath
- self.FilePath = FilePath
-
- self.originalSysModules = set(sys.modules.keys())
- self.savedSysPath = sys.path[:]
-
- self.pathDir = tempfile.mkdtemp()
- self.makeImportable(self.pathDir)
-
- def tearDown(self):
- super(_WritesPythonModules, self).tearDown()
-
- sys.path[:] = self.savedSysPath
- modulesToDelete = six.viewkeys(sys.modules) - self.originalSysModules
- for module in modulesToDelete:
- del sys.modules[module]
-
- shutil.rmtree(self.pathDir)
-
- def makeImportable(self, path):
- sys.path.append(path)
-
- def writeSourceInto(self, source, path, moduleName):
- directory = self.FilePath(path)
-
- module = directory.child(moduleName)
- # FilePath always opens a file in binary mode - but that will
- # break on Python 3
- with open(module.path, 'w') as f:
- f.write(textwrap.dedent(source))
-
- return self.PythonPath([directory.path])
-
- def makeModule(self, source, path, moduleName):
- pythonModuleName, _ = os.path.splitext(moduleName)
- return self.writeSourceInto(source, path, moduleName)[pythonModuleName]
-
- def attributesAsDict(self, hasIterAttributes):
- return {attr.name: attr for attr in hasIterAttributes.iterAttributes()}
-
- def loadModuleAsDict(self, module):
- module.load()
- return self.attributesAsDict(module)
-
- def makeModuleAsDict(self, source, path, name):
- return self.loadModuleAsDict(self.makeModule(source, path, name))
-
-
- @skipIf(not isTwistedInstalled(), "Twisted is not installed.")
- class OriginalLocationTests(_WritesPythonModules):
- """
- Tests that L{isOriginalLocation} detects when a
- L{PythonAttribute}'s FQPN refers to an object inside the module
- where it was defined.
-
- For example: A L{twisted.python.modules.PythonAttribute} with a
- name of 'foo.bar' that refers to a 'bar' object defined in module
- 'baz' does *not* refer to bar's original location, while a
- L{PythonAttribute} with a name of 'baz.bar' does.
-
- """
- def setUp(self):
- super(OriginalLocationTests, self).setUp()
- from .._discover import isOriginalLocation
- self.isOriginalLocation = isOriginalLocation
-
- def test_failsWithNoModule(self):
- """
- L{isOriginalLocation} returns False when the attribute refers to an
- object whose source module cannot be determined.
- """
- source = """\
- class Fake(object):
- pass
- hasEmptyModule = Fake()
- hasEmptyModule.__module__ = None
- """
-
- moduleDict = self.makeModuleAsDict(source,
- self.pathDir,
- 'empty_module_attr.py')
-
- self.assertFalse(self.isOriginalLocation(
- moduleDict['empty_module_attr.hasEmptyModule']))
-
- def test_failsWithDifferentModule(self):
- """
- L{isOriginalLocation} returns False when the attribute refers to
- an object outside of the module where that object was defined.
- """
- originalSource = """\
- class ImportThisClass(object):
- pass
- importThisObject = ImportThisClass()
- importThisNestingObject = ImportThisClass()
- importThisNestingObject.nestedObject = ImportThisClass()
- """
-
- importingSource = """\
- from original import (ImportThisClass,
- importThisObject,
- importThisNestingObject)
- """
-
- self.makeModule(originalSource, self.pathDir, 'original.py')
- importingDict = self.makeModuleAsDict(importingSource,
- self.pathDir,
- 'importing.py')
- self.assertFalse(
- self.isOriginalLocation(
- importingDict['importing.ImportThisClass']))
- self.assertFalse(
- self.isOriginalLocation(
- importingDict['importing.importThisObject']))
-
- nestingObject = importingDict['importing.importThisNestingObject']
- nestingObjectDict = self.attributesAsDict(nestingObject)
- nestedObject = nestingObjectDict[
- 'importing.importThisNestingObject.nestedObject']
-
- self.assertFalse(self.isOriginalLocation(nestedObject))
-
- def test_succeedsWithSameModule(self):
- """
- L{isOriginalLocation} returns True when the attribute refers to an
- object inside the module where that object was defined.
- """
- mSource = textwrap.dedent("""
- class ThisClassWasDefinedHere(object):
- pass
- anObject = ThisClassWasDefinedHere()
- aNestingObject = ThisClassWasDefinedHere()
- aNestingObject.nestedObject = ThisClassWasDefinedHere()
- """)
- mDict = self.makeModuleAsDict(mSource, self.pathDir, 'm.py')
- self.assertTrue(self.isOriginalLocation(
- mDict['m.ThisClassWasDefinedHere']))
- self.assertTrue(self.isOriginalLocation(mDict['m.aNestingObject']))
-
- nestingObject = mDict['m.aNestingObject']
- nestingObjectDict = self.attributesAsDict(nestingObject)
- nestedObject = nestingObjectDict['m.aNestingObject.nestedObject']
-
- self.assertTrue(self.isOriginalLocation(nestedObject))
-
-
- @skipIf(not isTwistedInstalled(), "Twisted is not installed.")
- class FindMachinesViaWrapperTests(_WritesPythonModules):
- """
- L{findMachinesViaWrapper} recursively yields FQPN,
- L{MethodicalMachine} pairs in and under a given
- L{twisted.python.modules.PythonModule} or
- L{twisted.python.modules.PythonAttribute}.
- """
- TEST_MODULE_SOURCE = """
- from automat import MethodicalMachine
-
-
- class PythonClass(object):
- _classMachine = MethodicalMachine()
-
- class NestedClass(object):
- _nestedClassMachine = MethodicalMachine()
-
- ignoredAttribute = "I am ignored."
-
- def ignoredMethod(self):
- "I am also ignored."
-
- rootLevelMachine = MethodicalMachine()
- ignoredPythonObject = PythonClass()
- anotherIgnoredPythonObject = "I am ignored."
- """
-
- def setUp(self):
- super(FindMachinesViaWrapperTests, self).setUp()
- from .._discover import findMachinesViaWrapper
- self.findMachinesViaWrapper = findMachinesViaWrapper
-
- def test_yieldsMachine(self):
- """
- When given a L{twisted.python.modules.PythonAttribute} that refers
- directly to a L{MethodicalMachine}, L{findMachinesViaWrapper}
- yields that machine and its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- rootMachine = MethodicalMachine()
- """
-
- moduleDict = self.makeModuleAsDict(source, self.pathDir, 'root.py')
- rootMachine = moduleDict['root.rootMachine']
- self.assertIn(('root.rootMachine', rootMachine.load()),
- list(self.findMachinesViaWrapper(rootMachine)))
-
- def test_yieldsMachineInClass(self):
- """
- When given a L{twisted.python.modules.PythonAttribute} that refers
- to a class that contains a L{MethodicalMachine} as a class
- variable, L{findMachinesViaWrapper} yields that machine and
- its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- class PythonClass(object):
- _classMachine = MethodicalMachine()
- """
- moduleDict = self.makeModuleAsDict(source, self.pathDir, 'clsmod.py')
- PythonClass = moduleDict['clsmod.PythonClass']
- self.assertIn(('clsmod.PythonClass._classMachine',
- PythonClass.load()._classMachine),
- list(self.findMachinesViaWrapper(PythonClass)))
-
- def test_yieldsMachineInNestedClass(self):
- """
- When given a L{twisted.python.modules.PythonAttribute} that refers
- to a nested class that contains a L{MethodicalMachine} as a
- class variable, L{findMachinesViaWrapper} yields that machine
- and its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- class PythonClass(object):
- class NestedClass(object):
- _classMachine = MethodicalMachine()
- """
- moduleDict = self.makeModuleAsDict(source,
- self.pathDir,
- 'nestedcls.py')
-
- PythonClass = moduleDict['nestedcls.PythonClass']
- self.assertIn(('nestedcls.PythonClass.NestedClass._classMachine',
- PythonClass.load().NestedClass._classMachine),
- list(self.findMachinesViaWrapper(PythonClass)))
-
- def test_yieldsMachineInModule(self):
- """
- When given a L{twisted.python.modules.PythonModule} that refers to
- a module that contains a L{MethodicalMachine},
- L{findMachinesViaWrapper} yields that machine and its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- rootMachine = MethodicalMachine()
- """
- module = self.makeModule(source, self.pathDir, 'root.py')
- rootMachine = self.loadModuleAsDict(module)['root.rootMachine'].load()
- self.assertIn(('root.rootMachine', rootMachine),
- list(self.findMachinesViaWrapper(module)))
-
- def test_yieldsMachineInClassInModule(self):
- """
- When given a L{twisted.python.modules.PythonModule} that refers to
- the original module of a class containing a
- L{MethodicalMachine}, L{findMachinesViaWrapper} yields that
- machine and its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- class PythonClass(object):
- _classMachine = MethodicalMachine()
- """
- module = self.makeModule(source, self.pathDir, 'clsmod.py')
- PythonClass = self.loadModuleAsDict(
- module)['clsmod.PythonClass'].load()
- self.assertIn(('clsmod.PythonClass._classMachine',
- PythonClass._classMachine),
- list(self.findMachinesViaWrapper(module)))
-
- def test_yieldsMachineInNestedClassInModule(self):
- """
- When given a L{twisted.python.modules.PythonModule} that refers to
- the original module of a nested class containing a
- L{MethodicalMachine}, L{findMachinesViaWrapper} yields that
- machine and its FQPN.
- """
- source = """\
- from automat import MethodicalMachine
-
- class PythonClass(object):
- class NestedClass(object):
- _classMachine = MethodicalMachine()
- """
- module = self.makeModule(source, self.pathDir, 'nestedcls.py')
- PythonClass = self.loadModuleAsDict(
- module)['nestedcls.PythonClass'].load()
-
- self.assertIn(('nestedcls.PythonClass.NestedClass._classMachine',
- PythonClass.NestedClass._classMachine),
- list(self.findMachinesViaWrapper(module)))
-
- def test_ignoresImportedClass(self):
- """
- When given a L{twisted.python.modules.PythonAttribute} that refers
- to a class imported from another module, any
- L{MethodicalMachine}s on that class are ignored.
-
- This behavior ensures that a machine is only discovered on a
- class when visiting the module where that class was defined.
- """
- originalSource = """
- from automat import MethodicalMachine
-
- class PythonClass(object):
- _classMachine = MethodicalMachine()
- """
-
- importingSource = """
- from original import PythonClass
- """
-
- self.makeModule(originalSource, self.pathDir, 'original.py')
- importingModule = self.makeModule(importingSource,
- self.pathDir,
- 'importing.py')
-
- self.assertFalse(list(self.findMachinesViaWrapper(importingModule)))
-
- def test_descendsIntoPackages(self):
- """
- L{findMachinesViaWrapper} descends into packages to discover
- machines.
- """
- pythonPath = self.PythonPath([self.pathDir])
- package = self.FilePath(self.pathDir).child("test_package")
- package.makedirs()
- package.child('__init__.py').touch()
-
- source = """
- from automat import MethodicalMachine
-
-
- class PythonClass(object):
- _classMachine = MethodicalMachine()
-
-
- rootMachine = MethodicalMachine()
- """
- self.makeModule(source, package.path, 'module.py')
-
- test_package = pythonPath['test_package']
- machines = sorted(self.findMachinesViaWrapper(test_package),
- key=operator.itemgetter(0))
-
- moduleDict = self.loadModuleAsDict(test_package['module'])
- rootMachine = moduleDict['test_package.module.rootMachine'].load()
- PythonClass = moduleDict['test_package.module.PythonClass'].load()
-
- expectedMachines = sorted(
- [('test_package.module.rootMachine',
- rootMachine),
- ('test_package.module.PythonClass._classMachine',
- PythonClass._classMachine)], key=operator.itemgetter(0))
-
- self.assertEqual(expectedMachines, machines)
-
- def test_infiniteLoop(self):
- """
- L{findMachinesViaWrapper} ignores infinite loops.
-
- Note this test can't fail - it can only run forever!
- """
- source = """
- class InfiniteLoop(object):
- pass
-
- InfiniteLoop.loop = InfiniteLoop
- """
- module = self.makeModule(source, self.pathDir, 'loop.py')
- self.assertFalse(list(self.findMachinesViaWrapper(module)))
-
-
- @skipIf(not isTwistedInstalled(), "Twisted is not installed.")
- class WrapFQPNTests(TestCase):
- """
- Tests that ensure L{wrapFQPN} loads the
- L{twisted.python.modules.PythonModule} or
- L{twisted.python.modules.PythonAttribute} for a given FQPN.
- """
-
- def setUp(self):
- from twisted.python.modules import PythonModule, PythonAttribute
- from .._discover import wrapFQPN, InvalidFQPN, NoModule, NoObject
-
- self.PythonModule = PythonModule
- self.PythonAttribute = PythonAttribute
- self.wrapFQPN = wrapFQPN
- self.InvalidFQPN = InvalidFQPN
- self.NoModule = NoModule
- self.NoObject = NoObject
-
- def assertModuleWrapperRefersTo(self, moduleWrapper, module):
- """
- Assert that a L{twisted.python.modules.PythonModule} refers to a
- particular Python module.
- """
- self.assertIsInstance(moduleWrapper, self.PythonModule)
- self.assertEqual(moduleWrapper.name, module.__name__)
- self.assertIs(moduleWrapper.load(), module)
-
- def assertAttributeWrapperRefersTo(self, attributeWrapper, fqpn, obj):
- """
- Assert that a L{twisted.python.modules.PythonAttribute} refers to a
- particular Python object.
- """
- self.assertIsInstance(attributeWrapper, self.PythonAttribute)
- self.assertEqual(attributeWrapper.name, fqpn)
- self.assertIs(attributeWrapper.load(), obj)
-
- def test_failsWithEmptyFQPN(self):
- """
- L{wrapFQPN} raises L{InvalidFQPN} when given an empty string.
- """
- with self.assertRaises(self.InvalidFQPN):
- self.wrapFQPN('')
-
- def test_failsWithBadDotting(self):
- """"
- L{wrapFQPN} raises L{InvalidFQPN} when given a badly-dotted
- FQPN. (e.g., x..y).
- """
- for bad in ('.fails', 'fails.', 'this..fails'):
- with self.assertRaises(self.InvalidFQPN):
- self.wrapFQPN(bad)
-
- def test_singleModule(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonModule}
- referring to the single module a dotless FQPN describes.
- """
- import os
-
- moduleWrapper = self.wrapFQPN('os')
-
- self.assertIsInstance(moduleWrapper, self.PythonModule)
- self.assertIs(moduleWrapper.load(), os)
-
- def test_failsWithMissingSingleModuleOrPackage(self):
- """
- L{wrapFQPN} raises L{NoModule} when given a dotless FQPN that does
- not refer to a module or package.
- """
- with self.assertRaises(self.NoModule):
- self.wrapFQPN("this is not an acceptable name!")
-
- def test_singlePackage(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonModule}
- referring to the single package a dotless FQPN describes.
- """
- import xml
- self.assertModuleWrapperRefersTo(self.wrapFQPN('xml'), xml)
-
- def test_multiplePackages(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonModule}
- referring to the deepest package described by dotted FQPN.
- """
- import xml.etree
- self.assertModuleWrapperRefersTo(self.wrapFQPN('xml.etree'), xml.etree)
-
- def test_multiplePackagesFinalModule(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonModule}
- referring to the deepest module described by dotted FQPN.
- """
- import xml.etree.ElementTree
- self.assertModuleWrapperRefersTo(
- self.wrapFQPN('xml.etree.ElementTree'), xml.etree.ElementTree)
-
- def test_singleModuleObject(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonAttribute}
- referring to the deepest object an FQPN names, traversing one module.
- """
- import os
- self.assertAttributeWrapperRefersTo(
- self.wrapFQPN('os.path'), 'os.path', os.path)
-
- def test_multiplePackagesObject(self):
- """
- L{wrapFQPN} returns a L{twisted.python.modules.PythonAttribute}
- referring to the deepest object described by an FQPN,
- descending through several packages.
- """
- import xml.etree.ElementTree
- import automat
-
- for fqpn, obj in [('xml.etree.ElementTree.fromstring',
- xml.etree.ElementTree.fromstring),
- ('automat.MethodicalMachine.__doc__',
- automat.MethodicalMachine.__doc__)]:
- self.assertAttributeWrapperRefersTo(
- self.wrapFQPN(fqpn), fqpn, obj)
-
- def test_failsWithMultiplePackagesMissingModuleOrPackage(self):
- """
- L{wrapFQPN} raises L{NoObject} when given an FQPN that contains a
- missing attribute, module, or package.
- """
- for bad in ('xml.etree.nope!',
- 'xml.etree.nope!.but.the.rest.is.believable'):
- with self.assertRaises(self.NoObject):
- self.wrapFQPN(bad)
-
-
- @skipIf(not isTwistedInstalled(), "Twisted is not installed.")
- class FindMachinesIntegrationTests(_WritesPythonModules):
- """
- Integration tests to check that L{findMachines} yields all
- machines discoverable at or below an FQPN.
- """
-
- SOURCE = """
- from automat import MethodicalMachine
-
- class PythonClass(object):
- _machine = MethodicalMachine()
- ignored = "i am ignored"
-
- rootLevel = MethodicalMachine()
-
- ignored = "i am ignored"
- """
-
- def setUp(self):
- super(FindMachinesIntegrationTests, self).setUp()
- from .._discover import findMachines
-
- self.findMachines = findMachines
-
- packageDir = self.FilePath(self.pathDir).child("test_package")
- packageDir.makedirs()
- self.pythonPath = self.PythonPath([self.pathDir])
- self.writeSourceInto(self.SOURCE, packageDir.path, '__init__.py')
-
- subPackageDir = packageDir.child('subpackage')
- subPackageDir.makedirs()
- subPackageDir.child('__init__.py').touch()
-
- self.makeModule(self.SOURCE, subPackageDir.path, 'module.py')
-
- self.packageDict = self.loadModuleAsDict(
- self.pythonPath['test_package'])
- self.moduleDict = self.loadModuleAsDict(
- self.pythonPath['test_package']['subpackage']['module'])
-
- def test_discoverAll(self):
- """
- Given a top-level package FQPN, L{findMachines} discovers all
- L{MethodicalMachine} instances in and below it.
- """
- machines = sorted(self.findMachines('test_package'),
- key=operator.itemgetter(0))
-
- tpRootLevel = self.packageDict['test_package.rootLevel'].load()
- tpPythonClass = self.packageDict['test_package.PythonClass'].load()
-
- mRLAttr = self.moduleDict['test_package.subpackage.module.rootLevel']
- mRootLevel = mRLAttr.load()
- mPCAttr = self.moduleDict['test_package.subpackage.module.PythonClass']
- mPythonClass = mPCAttr.load()
-
- expectedMachines = sorted(
- [('test_package.rootLevel', tpRootLevel),
- ('test_package.PythonClass._machine', tpPythonClass._machine),
- ('test_package.subpackage.module.rootLevel', mRootLevel),
- ('test_package.subpackage.module.PythonClass._machine',
- mPythonClass._machine)],
- key=operator.itemgetter(0))
-
- self.assertEqual(expectedMachines, machines)
|