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