test_autoreload.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. """Tests for autoreload extension.
  2. """
  3. #-----------------------------------------------------------------------------
  4. # Copyright (c) 2012 IPython Development Team.
  5. #
  6. # Distributed under the terms of the Modified BSD License.
  7. #
  8. # The full license is in the file COPYING.txt, distributed with this software.
  9. #-----------------------------------------------------------------------------
  10. #-----------------------------------------------------------------------------
  11. # Imports
  12. #-----------------------------------------------------------------------------
  13. import os
  14. import sys
  15. import tempfile
  16. import shutil
  17. import random
  18. import time
  19. import nose.tools as nt
  20. import IPython.testing.tools as tt
  21. from IPython.extensions.autoreload import AutoreloadMagics
  22. from IPython.core.events import EventManager, pre_run_cell
  23. from IPython.utils.py3compat import PY3
  24. if PY3:
  25. from io import StringIO
  26. else:
  27. from StringIO import StringIO
  28. #-----------------------------------------------------------------------------
  29. # Test fixture
  30. #-----------------------------------------------------------------------------
  31. noop = lambda *a, **kw: None
  32. class FakeShell(object):
  33. def __init__(self):
  34. self.ns = {}
  35. self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
  36. self.auto_magics = AutoreloadMagics(shell=self)
  37. self.events.register('pre_run_cell', self.auto_magics.pre_run_cell)
  38. register_magics = set_hook = noop
  39. def run_code(self, code):
  40. self.events.trigger('pre_run_cell')
  41. exec(code, self.ns)
  42. self.auto_magics.post_execute_hook()
  43. def push(self, items):
  44. self.ns.update(items)
  45. def magic_autoreload(self, parameter):
  46. self.auto_magics.autoreload(parameter)
  47. def magic_aimport(self, parameter, stream=None):
  48. self.auto_magics.aimport(parameter, stream=stream)
  49. self.auto_magics.post_execute_hook()
  50. class Fixture(object):
  51. """Fixture for creating test module files"""
  52. test_dir = None
  53. old_sys_path = None
  54. filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
  55. def setUp(self):
  56. self.test_dir = tempfile.mkdtemp()
  57. self.old_sys_path = list(sys.path)
  58. sys.path.insert(0, self.test_dir)
  59. self.shell = FakeShell()
  60. def tearDown(self):
  61. shutil.rmtree(self.test_dir)
  62. sys.path = self.old_sys_path
  63. self.test_dir = None
  64. self.old_sys_path = None
  65. self.shell = None
  66. def get_module(self):
  67. module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
  68. if module_name in sys.modules:
  69. del sys.modules[module_name]
  70. file_name = os.path.join(self.test_dir, module_name + ".py")
  71. return module_name, file_name
  72. def write_file(self, filename, content):
  73. """
  74. Write a file, and force a timestamp difference of at least one second
  75. Notes
  76. -----
  77. Python's .pyc files record the timestamp of their compilation
  78. with a time resolution of one second.
  79. Therefore, we need to force a timestamp difference between .py
  80. and .pyc, without having the .py file be timestamped in the
  81. future, and without changing the timestamp of the .pyc file
  82. (because that is stored in the file). The only reliable way
  83. to achieve this seems to be to sleep.
  84. """
  85. # Sleep one second + eps
  86. time.sleep(1.05)
  87. # Write
  88. f = open(filename, 'w')
  89. try:
  90. f.write(content)
  91. finally:
  92. f.close()
  93. def new_module(self, code):
  94. mod_name, mod_fn = self.get_module()
  95. f = open(mod_fn, 'w')
  96. try:
  97. f.write(code)
  98. finally:
  99. f.close()
  100. return mod_name, mod_fn
  101. #-----------------------------------------------------------------------------
  102. # Test automatic reloading
  103. #-----------------------------------------------------------------------------
  104. class TestAutoreload(Fixture):
  105. def _check_smoketest(self, use_aimport=True):
  106. """
  107. Functional test for the automatic reloader using either
  108. '%autoreload 1' or '%autoreload 2'
  109. """
  110. mod_name, mod_fn = self.new_module("""
  111. x = 9
  112. z = 123 # this item will be deleted
  113. def foo(y):
  114. return y + 3
  115. class Baz(object):
  116. def __init__(self, x):
  117. self.x = x
  118. def bar(self, y):
  119. return self.x + y
  120. @property
  121. def quux(self):
  122. return 42
  123. def zzz(self):
  124. '''This method will be deleted below'''
  125. return 99
  126. class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
  127. def foo(self):
  128. return 1
  129. """)
  130. #
  131. # Import module, and mark for reloading
  132. #
  133. if use_aimport:
  134. self.shell.magic_autoreload("1")
  135. self.shell.magic_aimport(mod_name)
  136. stream = StringIO()
  137. self.shell.magic_aimport("", stream=stream)
  138. nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
  139. with nt.assert_raises(ImportError):
  140. self.shell.magic_aimport("tmpmod_as318989e89ds")
  141. else:
  142. self.shell.magic_autoreload("2")
  143. self.shell.run_code("import %s" % mod_name)
  144. stream = StringIO()
  145. self.shell.magic_aimport("", stream=stream)
  146. nt.assert_true("Modules to reload:\nall-except-skipped" in
  147. stream.getvalue())
  148. nt.assert_in(mod_name, self.shell.ns)
  149. mod = sys.modules[mod_name]
  150. #
  151. # Test module contents
  152. #
  153. old_foo = mod.foo
  154. old_obj = mod.Baz(9)
  155. old_obj2 = mod.Bar()
  156. def check_module_contents():
  157. nt.assert_equal(mod.x, 9)
  158. nt.assert_equal(mod.z, 123)
  159. nt.assert_equal(old_foo(0), 3)
  160. nt.assert_equal(mod.foo(0), 3)
  161. obj = mod.Baz(9)
  162. nt.assert_equal(old_obj.bar(1), 10)
  163. nt.assert_equal(obj.bar(1), 10)
  164. nt.assert_equal(obj.quux, 42)
  165. nt.assert_equal(obj.zzz(), 99)
  166. obj2 = mod.Bar()
  167. nt.assert_equal(old_obj2.foo(), 1)
  168. nt.assert_equal(obj2.foo(), 1)
  169. check_module_contents()
  170. #
  171. # Simulate a failed reload: no reload should occur and exactly
  172. # one error message should be printed
  173. #
  174. self.write_file(mod_fn, """
  175. a syntax error
  176. """)
  177. with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
  178. self.shell.run_code("pass") # trigger reload
  179. with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
  180. self.shell.run_code("pass") # trigger another reload
  181. check_module_contents()
  182. #
  183. # Rewrite module (this time reload should succeed)
  184. #
  185. self.write_file(mod_fn, """
  186. x = 10
  187. def foo(y):
  188. return y + 4
  189. class Baz(object):
  190. def __init__(self, x):
  191. self.x = x
  192. def bar(self, y):
  193. return self.x + y + 1
  194. @property
  195. def quux(self):
  196. return 43
  197. class Bar: # old-style class
  198. def foo(self):
  199. return 2
  200. """)
  201. def check_module_contents():
  202. nt.assert_equal(mod.x, 10)
  203. nt.assert_false(hasattr(mod, 'z'))
  204. nt.assert_equal(old_foo(0), 4) # superreload magic!
  205. nt.assert_equal(mod.foo(0), 4)
  206. obj = mod.Baz(9)
  207. nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
  208. nt.assert_equal(obj.bar(1), 11)
  209. nt.assert_equal(old_obj.quux, 43)
  210. nt.assert_equal(obj.quux, 43)
  211. nt.assert_false(hasattr(old_obj, 'zzz'))
  212. nt.assert_false(hasattr(obj, 'zzz'))
  213. obj2 = mod.Bar()
  214. nt.assert_equal(old_obj2.foo(), 2)
  215. nt.assert_equal(obj2.foo(), 2)
  216. self.shell.run_code("pass") # trigger reload
  217. check_module_contents()
  218. #
  219. # Another failure case: deleted file (shouldn't reload)
  220. #
  221. os.unlink(mod_fn)
  222. self.shell.run_code("pass") # trigger reload
  223. check_module_contents()
  224. #
  225. # Disable autoreload and rewrite module: no reload should occur
  226. #
  227. if use_aimport:
  228. self.shell.magic_aimport("-" + mod_name)
  229. stream = StringIO()
  230. self.shell.magic_aimport("", stream=stream)
  231. nt.assert_true(("Modules to skip:\n%s" % mod_name) in
  232. stream.getvalue())
  233. # This should succeed, although no such module exists
  234. self.shell.magic_aimport("-tmpmod_as318989e89ds")
  235. else:
  236. self.shell.magic_autoreload("0")
  237. self.write_file(mod_fn, """
  238. x = -99
  239. """)
  240. self.shell.run_code("pass") # trigger reload
  241. self.shell.run_code("pass")
  242. check_module_contents()
  243. #
  244. # Re-enable autoreload: reload should now occur
  245. #
  246. if use_aimport:
  247. self.shell.magic_aimport(mod_name)
  248. else:
  249. self.shell.magic_autoreload("")
  250. self.shell.run_code("pass") # trigger reload
  251. nt.assert_equal(mod.x, -99)
  252. def test_smoketest_aimport(self):
  253. self._check_smoketest(use_aimport=True)
  254. def test_smoketest_autoreload(self):
  255. self._check_smoketest(use_aimport=False)