test_rebuild.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. import sys, os
  4. import types
  5. from twisted.trial import unittest
  6. from twisted.python import rebuild
  7. import crash_test_dummy
  8. f = crash_test_dummy.foo
  9. class Foo: pass
  10. class Bar(Foo): pass
  11. class Baz(object): pass
  12. class Buz(Bar, Baz): pass
  13. class HashRaisesRuntimeError:
  14. """
  15. Things that don't hash (raise an Exception) should be ignored by the
  16. rebuilder.
  17. @ivar hashCalled: C{bool} set to True when __hash__ is called.
  18. """
  19. def __init__(self):
  20. self.hashCalled = False
  21. def __hash__(self):
  22. self.hashCalled = True
  23. raise RuntimeError('not a TypeError!')
  24. unhashableObject = None # set in test_hashException
  25. class RebuildTests(unittest.TestCase):
  26. """
  27. Simple testcase for rebuilding, to at least exercise the code.
  28. """
  29. def setUp(self):
  30. self.libPath = self.mktemp()
  31. os.mkdir(self.libPath)
  32. self.fakelibPath = os.path.join(self.libPath, 'twisted_rebuild_fakelib')
  33. os.mkdir(self.fakelibPath)
  34. open(os.path.join(self.fakelibPath, '__init__.py'), 'w').close()
  35. sys.path.insert(0, self.libPath)
  36. def tearDown(self):
  37. sys.path.remove(self.libPath)
  38. def testFileRebuild(self):
  39. from twisted.python.util import sibpath
  40. import shutil, time
  41. shutil.copyfile(sibpath(__file__, "myrebuilder1.py"),
  42. os.path.join(self.fakelibPath, "myrebuilder.py"))
  43. from twisted_rebuild_fakelib import myrebuilder
  44. a = myrebuilder.A()
  45. try:
  46. object
  47. except NameError:
  48. pass
  49. else:
  50. from twisted.test import test_rebuild
  51. b = myrebuilder.B()
  52. class C(myrebuilder.B):
  53. pass
  54. test_rebuild.C = C
  55. C()
  56. i = myrebuilder.Inherit()
  57. self.assertEqual(a.a(), 'a')
  58. # necessary because the file has not "changed" if a second has not gone
  59. # by in unix. This sucks, but it's not often that you'll be doing more
  60. # than one reload per second.
  61. time.sleep(1.1)
  62. shutil.copyfile(sibpath(__file__, "myrebuilder2.py"),
  63. os.path.join(self.fakelibPath, "myrebuilder.py"))
  64. rebuild.rebuild(myrebuilder)
  65. try:
  66. object
  67. except NameError:
  68. pass
  69. else:
  70. b2 = myrebuilder.B()
  71. self.assertEqual(b2.b(), 'c')
  72. self.assertEqual(b.b(), 'c')
  73. self.assertEqual(i.a(), 'd')
  74. self.assertEqual(a.a(), 'b')
  75. # more work to be done on new-style classes
  76. # self.assertEqual(c.b(), 'c')
  77. def testRebuild(self):
  78. """
  79. Rebuilding an unchanged module.
  80. """
  81. # This test would actually pass if rebuild was a no-op, but it
  82. # ensures rebuild doesn't break stuff while being a less
  83. # complex test than testFileRebuild.
  84. x = crash_test_dummy.X('a')
  85. rebuild.rebuild(crash_test_dummy, doLog=False)
  86. # Instance rebuilding is triggered by attribute access.
  87. x.do()
  88. self.failUnlessIdentical(x.__class__, crash_test_dummy.X)
  89. self.failUnlessIdentical(f, crash_test_dummy.foo)
  90. def testComponentInteraction(self):
  91. x = crash_test_dummy.XComponent()
  92. x.setAdapter(crash_test_dummy.IX, crash_test_dummy.XA)
  93. x.getComponent(crash_test_dummy.IX)
  94. rebuild.rebuild(crash_test_dummy, 0)
  95. newComponent = x.getComponent(crash_test_dummy.IX)
  96. newComponent.method()
  97. self.assertEqual(newComponent.__class__, crash_test_dummy.XA)
  98. # Test that a duplicate registerAdapter is not allowed
  99. from twisted.python import components
  100. self.assertRaises(ValueError, components.registerAdapter,
  101. crash_test_dummy.XA, crash_test_dummy.X,
  102. crash_test_dummy.IX)
  103. def testUpdateInstance(self):
  104. global Foo, Buz
  105. b = Buz()
  106. class Foo:
  107. def foo(self):
  108. pass
  109. class Buz(Bar, Baz):
  110. x = 10
  111. rebuild.updateInstance(b)
  112. assert hasattr(b, 'foo'), "Missing method on rebuilt instance"
  113. assert hasattr(b, 'x'), "Missing class attribute on rebuilt instance"
  114. def testBananaInteraction(self):
  115. from twisted.python import rebuild
  116. from twisted.spread import banana
  117. rebuild.latestClass(banana.Banana)
  118. def test_hashException(self):
  119. """
  120. Rebuilding something that has a __hash__ that raises a non-TypeError
  121. shouldn't cause rebuild to die.
  122. """
  123. global unhashableObject
  124. unhashableObject = HashRaisesRuntimeError()
  125. def _cleanup():
  126. global unhashableObject
  127. unhashableObject = None
  128. self.addCleanup(_cleanup)
  129. rebuild.rebuild(rebuild)
  130. self.assertTrue(unhashableObject.hashCalled)
  131. class NewStyleTests(unittest.TestCase):
  132. """
  133. Tests for rebuilding new-style classes of various sorts.
  134. """
  135. def setUp(self):
  136. self.m = types.ModuleType('whipping')
  137. sys.modules['whipping'] = self.m
  138. def tearDown(self):
  139. del sys.modules['whipping']
  140. del self.m
  141. def test_slots(self):
  142. """
  143. Try to rebuild a new style class with slots defined.
  144. """
  145. classDefinition = (
  146. "class SlottedClass(object):\n"
  147. " __slots__ = ['a']\n")
  148. exec(classDefinition, self.m.__dict__)
  149. inst = self.m.SlottedClass()
  150. inst.a = 7
  151. exec(classDefinition, self.m.__dict__)
  152. rebuild.updateInstance(inst)
  153. self.assertEqual(inst.a, 7)
  154. self.assertIs(type(inst), self.m.SlottedClass)
  155. def test_typeSubclass(self):
  156. """
  157. Try to rebuild a base type subclass.
  158. """
  159. classDefinition = (
  160. "class ListSubclass(list):\n"
  161. " pass\n")
  162. exec(classDefinition, self.m.__dict__)
  163. inst = self.m.ListSubclass()
  164. inst.append(2)
  165. exec(classDefinition, self.m.__dict__)
  166. rebuild.updateInstance(inst)
  167. self.assertEqual(inst[0], 2)
  168. self.assertIs(type(inst), self.m.ListSubclass)
  169. def test_instanceSlots(self):
  170. """
  171. Test that when rebuilding an instance with a __slots__ attribute, it
  172. fails accurately instead of giving a L{rebuild.RebuildError}.
  173. """
  174. classDefinition = (
  175. "class NotSlottedClass(object):\n"
  176. " pass\n")
  177. exec(classDefinition, self.m.__dict__)
  178. inst = self.m.NotSlottedClass()
  179. inst.__slots__ = ['a']
  180. classDefinition = (
  181. "class NotSlottedClass:\n"
  182. " pass\n")
  183. exec(classDefinition, self.m.__dict__)
  184. # Moving from new-style class to old-style should fail.
  185. self.assertRaises(TypeError, rebuild.updateInstance, inst)