##// END OF EJS Templates
test both old and new classes
Matthias Bussonnier -
Show More
@@ -1,399 +1,404 b''
1 """Tests for autoreload extension.
1 """Tests for autoreload extension.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012 IPython Development Team.
4 # Copyright (c) 2012 IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18 import textwrap
18 import textwrap
19 import shutil
19 import shutil
20 import random
20 import random
21 import time
21 import time
22 from io import StringIO
22 from io import StringIO
23
23
24 import nose.tools as nt
24 import nose.tools as nt
25 import IPython.testing.tools as tt
25 import IPython.testing.tools as tt
26
26
27 from IPython.testing.decorators import skipif
27 from IPython.testing.decorators import skipif
28
28
29 from IPython.extensions.autoreload import AutoreloadMagics
29 from IPython.extensions.autoreload import AutoreloadMagics
30 from IPython.core.events import EventManager, pre_run_cell
30 from IPython.core.events import EventManager, pre_run_cell
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Test fixture
33 # Test fixture
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 noop = lambda *a, **kw: None
36 noop = lambda *a, **kw: None
37
37
38 class FakeShell(object):
38 class FakeShell(object):
39
39
40 def __init__(self):
40 def __init__(self):
41 self.ns = {}
41 self.ns = {}
42 self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
42 self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
43 self.auto_magics = AutoreloadMagics(shell=self)
43 self.auto_magics = AutoreloadMagics(shell=self)
44 self.events.register('pre_run_cell', self.auto_magics.pre_run_cell)
44 self.events.register('pre_run_cell', self.auto_magics.pre_run_cell)
45
45
46 register_magics = set_hook = noop
46 register_magics = set_hook = noop
47
47
48 def run_code(self, code):
48 def run_code(self, code):
49 self.events.trigger('pre_run_cell')
49 self.events.trigger('pre_run_cell')
50 exec(code, self.ns)
50 exec(code, self.ns)
51 self.auto_magics.post_execute_hook()
51 self.auto_magics.post_execute_hook()
52
52
53 def push(self, items):
53 def push(self, items):
54 self.ns.update(items)
54 self.ns.update(items)
55
55
56 def magic_autoreload(self, parameter):
56 def magic_autoreload(self, parameter):
57 self.auto_magics.autoreload(parameter)
57 self.auto_magics.autoreload(parameter)
58
58
59 def magic_aimport(self, parameter, stream=None):
59 def magic_aimport(self, parameter, stream=None):
60 self.auto_magics.aimport(parameter, stream=stream)
60 self.auto_magics.aimport(parameter, stream=stream)
61 self.auto_magics.post_execute_hook()
61 self.auto_magics.post_execute_hook()
62
62
63
63
64 class Fixture(object):
64 class Fixture(object):
65 """Fixture for creating test module files"""
65 """Fixture for creating test module files"""
66
66
67 test_dir = None
67 test_dir = None
68 old_sys_path = None
68 old_sys_path = None
69 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
69 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
70
70
71 def setUp(self):
71 def setUp(self):
72 self.test_dir = tempfile.mkdtemp()
72 self.test_dir = tempfile.mkdtemp()
73 self.old_sys_path = list(sys.path)
73 self.old_sys_path = list(sys.path)
74 sys.path.insert(0, self.test_dir)
74 sys.path.insert(0, self.test_dir)
75 self.shell = FakeShell()
75 self.shell = FakeShell()
76
76
77 def tearDown(self):
77 def tearDown(self):
78 shutil.rmtree(self.test_dir)
78 shutil.rmtree(self.test_dir)
79 sys.path = self.old_sys_path
79 sys.path = self.old_sys_path
80
80
81 self.test_dir = None
81 self.test_dir = None
82 self.old_sys_path = None
82 self.old_sys_path = None
83 self.shell = None
83 self.shell = None
84
84
85 def get_module(self):
85 def get_module(self):
86 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
86 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
87 if module_name in sys.modules:
87 if module_name in sys.modules:
88 del sys.modules[module_name]
88 del sys.modules[module_name]
89 file_name = os.path.join(self.test_dir, module_name + ".py")
89 file_name = os.path.join(self.test_dir, module_name + ".py")
90 return module_name, file_name
90 return module_name, file_name
91
91
92 def write_file(self, filename, content):
92 def write_file(self, filename, content):
93 """
93 """
94 Write a file, and force a timestamp difference of at least one second
94 Write a file, and force a timestamp difference of at least one second
95
95
96 Notes
96 Notes
97 -----
97 -----
98 Python's .pyc files record the timestamp of their compilation
98 Python's .pyc files record the timestamp of their compilation
99 with a time resolution of one second.
99 with a time resolution of one second.
100
100
101 Therefore, we need to force a timestamp difference between .py
101 Therefore, we need to force a timestamp difference between .py
102 and .pyc, without having the .py file be timestamped in the
102 and .pyc, without having the .py file be timestamped in the
103 future, and without changing the timestamp of the .pyc file
103 future, and without changing the timestamp of the .pyc file
104 (because that is stored in the file). The only reliable way
104 (because that is stored in the file). The only reliable way
105 to achieve this seems to be to sleep.
105 to achieve this seems to be to sleep.
106 """
106 """
107
107
108 # Sleep one second + eps
108 # Sleep one second + eps
109 time.sleep(1.05)
109 time.sleep(1.05)
110
110
111 # Write
111 # Write
112 f = open(filename, 'w')
112 f = open(filename, 'w')
113 try:
113 try:
114 f.write(content)
114 f.write(content)
115 finally:
115 finally:
116 f.close()
116 f.close()
117
117
118 def new_module(self, code):
118 def new_module(self, code):
119 mod_name, mod_fn = self.get_module()
119 mod_name, mod_fn = self.get_module()
120 f = open(mod_fn, 'w')
120 f = open(mod_fn, 'w')
121 try:
121 try:
122 f.write(code)
122 f.write(code)
123 finally:
123 finally:
124 f.close()
124 f.close()
125 return mod_name, mod_fn
125 return mod_name, mod_fn
126
126
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128 # Test automatic reloading
128 # Test automatic reloading
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130
130
131 class TestAutoreload(Fixture):
131 class TestAutoreload(Fixture):
132
132
133 @skipif(sys.version_info < (3, 6))
133 @skipif(sys.version_info < (3, 6))
134 def test_reload_enums(self):
134 def test_reload_enums(self):
135 import enum
135 import enum
136 mod_name, mod_fn = self.new_module(textwrap.dedent("""
136 mod_name, mod_fn = self.new_module(textwrap.dedent("""
137 from enum import Enum
137 from enum import Enum
138 class MyEnum(Enum):
138 class MyEnum(Enum):
139 A = 'A'
139 A = 'A'
140 B = 'B'
140 B = 'B'
141 """))
141 """))
142 self.shell.magic_autoreload("2")
142 self.shell.magic_autoreload("2")
143 self.shell.magic_aimport(mod_name)
143 self.shell.magic_aimport(mod_name)
144 self.write_file(mod_fn, textwrap.dedent("""
144 self.write_file(mod_fn, textwrap.dedent("""
145 from enum import Enum
145 from enum import Enum
146 class MyEnum(Enum):
146 class MyEnum(Enum):
147 A = 'A'
147 A = 'A'
148 B = 'B'
148 B = 'B'
149 C = 'C'
149 C = 'C'
150 """))
150 """))
151 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
151 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
152 self.shell.run_code("pass") # trigger another reload
152 self.shell.run_code("pass") # trigger another reload
153
153
154 def test_reload_class_attributes(self):
154 def test_reload_class_attributes(self):
155 self.shell.magic_autoreload("2")
155 self.shell.magic_autoreload("2")
156 mod_name, mod_fn = self.new_module(textwrap.dedent("""
156 mod_name, mod_fn = self.new_module(textwrap.dedent("""
157 class MyClass:
157 class MyClass:
158
158
159 def __init__(self, a=10):
159 def __init__(self, a=10):
160 self.a = a
160 self.a = a
161 self.b = 22
161 self.b = 22
162 # self.toto = 33
162 # self.toto = 33
163
163
164 def square(self):
164 def square(self):
165 print('compute square')
165 print('compute square')
166 return self.a*self.a
166 return self.a*self.a
167 """))
167 """
168 )
169 )
168 self.shell.run_code("from %s import MyClass" % mod_name)
170 self.shell.run_code("from %s import MyClass" % mod_name)
169 self.shell.run_code("c = MyClass(5)")
171 self.shell.run_code("first = MyClass(5)")
170 self.shell.run_code("c.square()")
172 self.shell.run_code("first.square()")
171 with nt.assert_raises(AttributeError):
173 with nt.assert_raises(AttributeError):
172 self.shell.run_code("c.cube()")
174 self.shell.run_code("first.cube()")
173 with nt.assert_raises(AttributeError):
175 with nt.assert_raises(AttributeError):
174 self.shell.run_code("c.power(5)")
176 self.shell.run_code("first.power(5)")
175 self.shell.run_code("c.b")
177 self.shell.run_code("first.b")
176 with nt.assert_raises(AttributeError):
178 with nt.assert_raises(AttributeError):
177 self.shell.run_code("c.toto")
179 self.shell.run_code("first.toto")
178
180
181 # remove square, add power
179
182
180 self.write_file(mod_fn, textwrap.dedent("""
183 self.write_file(
184 mod_fn,
185 textwrap.dedent(
186 """
181 class MyClass:
187 class MyClass:
182
188
183 def __init__(self, a=10):
189 def __init__(self, a=10):
184 self.a = a
190 self.a = a
185 self.b = 11
191 self.b = 11
186
192
187 def power(self, p):
193 def power(self, p):
188 print('compute power '+str(p))
194 print('compute power '+str(p))
189 return self.a**p
195 return self.a**p
190 """))
196 """
191
197 ),
192 self.shell.run_code("d = MyClass(5)")
198 )
193 self.shell.run_code("d.power(5)")
199
194 with nt.assert_raises(AttributeError):
200 self.shell.run_code("second = MyClass(5)")
195 self.shell.run_code("c.cube()")
201
196 with nt.assert_raises(AttributeError):
202 for object_name in {'first', 'second'}:
197 self.shell.run_code("c.square(5)")
203 self.shell.run_code("{object_name}.power(5)".format(object_name=object_name))
198 self.shell.run_code("c.b")
204 with nt.assert_raises(AttributeError):
199 self.shell.run_code("c.a")
205 self.shell.run_code("{object_name}.cube()".format(object_name=object_name))
200 with nt.assert_raises(AttributeError):
206 with nt.assert_raises(AttributeError):
201 self.shell.run_code("c.toto")
207 self.shell.run_code("{object_name}.square(5)".format(object_name=object_name))
202
208 self.shell.run_code("{object_name}.b".format(object_name=object_name))
203
209 self.shell.run_code("{object_name}.a".format(object_name=object_name))
204
210 with nt.assert_raises(AttributeError):
205
211 self.shell.run_code("{object_name}.toto".format(object_name=object_name))
206
207
212
208 def _check_smoketest(self, use_aimport=True):
213 def _check_smoketest(self, use_aimport=True):
209 """
214 """
210 Functional test for the automatic reloader using either
215 Functional test for the automatic reloader using either
211 '%autoreload 1' or '%autoreload 2'
216 '%autoreload 1' or '%autoreload 2'
212 """
217 """
213
218
214 mod_name, mod_fn = self.new_module("""
219 mod_name, mod_fn = self.new_module("""
215 x = 9
220 x = 9
216
221
217 z = 123 # this item will be deleted
222 z = 123 # this item will be deleted
218
223
219 def foo(y):
224 def foo(y):
220 return y + 3
225 return y + 3
221
226
222 class Baz(object):
227 class Baz(object):
223 def __init__(self, x):
228 def __init__(self, x):
224 self.x = x
229 self.x = x
225 def bar(self, y):
230 def bar(self, y):
226 return self.x + y
231 return self.x + y
227 @property
232 @property
228 def quux(self):
233 def quux(self):
229 return 42
234 return 42
230 def zzz(self):
235 def zzz(self):
231 '''This method will be deleted below'''
236 '''This method will be deleted below'''
232 return 99
237 return 99
233
238
234 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
239 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
235 def foo(self):
240 def foo(self):
236 return 1
241 return 1
237 """)
242 """)
238
243
239 #
244 #
240 # Import module, and mark for reloading
245 # Import module, and mark for reloading
241 #
246 #
242 if use_aimport:
247 if use_aimport:
243 self.shell.magic_autoreload("1")
248 self.shell.magic_autoreload("1")
244 self.shell.magic_aimport(mod_name)
249 self.shell.magic_aimport(mod_name)
245 stream = StringIO()
250 stream = StringIO()
246 self.shell.magic_aimport("", stream=stream)
251 self.shell.magic_aimport("", stream=stream)
247 nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
252 nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
248
253
249 with nt.assert_raises(ImportError):
254 with nt.assert_raises(ImportError):
250 self.shell.magic_aimport("tmpmod_as318989e89ds")
255 self.shell.magic_aimport("tmpmod_as318989e89ds")
251 else:
256 else:
252 self.shell.magic_autoreload("2")
257 self.shell.magic_autoreload("2")
253 self.shell.run_code("import %s" % mod_name)
258 self.shell.run_code("import %s" % mod_name)
254 stream = StringIO()
259 stream = StringIO()
255 self.shell.magic_aimport("", stream=stream)
260 self.shell.magic_aimport("", stream=stream)
256 nt.assert_true("Modules to reload:\nall-except-skipped" in
261 nt.assert_true("Modules to reload:\nall-except-skipped" in
257 stream.getvalue())
262 stream.getvalue())
258 nt.assert_in(mod_name, self.shell.ns)
263 nt.assert_in(mod_name, self.shell.ns)
259
264
260 mod = sys.modules[mod_name]
265 mod = sys.modules[mod_name]
261
266
262 #
267 #
263 # Test module contents
268 # Test module contents
264 #
269 #
265 old_foo = mod.foo
270 old_foo = mod.foo
266 old_obj = mod.Baz(9)
271 old_obj = mod.Baz(9)
267 old_obj2 = mod.Bar()
272 old_obj2 = mod.Bar()
268
273
269 def check_module_contents():
274 def check_module_contents():
270 nt.assert_equal(mod.x, 9)
275 nt.assert_equal(mod.x, 9)
271 nt.assert_equal(mod.z, 123)
276 nt.assert_equal(mod.z, 123)
272
277
273 nt.assert_equal(old_foo(0), 3)
278 nt.assert_equal(old_foo(0), 3)
274 nt.assert_equal(mod.foo(0), 3)
279 nt.assert_equal(mod.foo(0), 3)
275
280
276 obj = mod.Baz(9)
281 obj = mod.Baz(9)
277 nt.assert_equal(old_obj.bar(1), 10)
282 nt.assert_equal(old_obj.bar(1), 10)
278 nt.assert_equal(obj.bar(1), 10)
283 nt.assert_equal(obj.bar(1), 10)
279 nt.assert_equal(obj.quux, 42)
284 nt.assert_equal(obj.quux, 42)
280 nt.assert_equal(obj.zzz(), 99)
285 nt.assert_equal(obj.zzz(), 99)
281
286
282 obj2 = mod.Bar()
287 obj2 = mod.Bar()
283 nt.assert_equal(old_obj2.foo(), 1)
288 nt.assert_equal(old_obj2.foo(), 1)
284 nt.assert_equal(obj2.foo(), 1)
289 nt.assert_equal(obj2.foo(), 1)
285
290
286 check_module_contents()
291 check_module_contents()
287
292
288 #
293 #
289 # Simulate a failed reload: no reload should occur and exactly
294 # Simulate a failed reload: no reload should occur and exactly
290 # one error message should be printed
295 # one error message should be printed
291 #
296 #
292 self.write_file(mod_fn, """
297 self.write_file(mod_fn, """
293 a syntax error
298 a syntax error
294 """)
299 """)
295
300
296 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
301 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
297 self.shell.run_code("pass") # trigger reload
302 self.shell.run_code("pass") # trigger reload
298 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
303 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
299 self.shell.run_code("pass") # trigger another reload
304 self.shell.run_code("pass") # trigger another reload
300 check_module_contents()
305 check_module_contents()
301
306
302 #
307 #
303 # Rewrite module (this time reload should succeed)
308 # Rewrite module (this time reload should succeed)
304 #
309 #
305 self.write_file(mod_fn, """
310 self.write_file(mod_fn, """
306 x = 10
311 x = 10
307
312
308 def foo(y):
313 def foo(y):
309 return y + 4
314 return y + 4
310
315
311 class Baz(object):
316 class Baz(object):
312 def __init__(self, x):
317 def __init__(self, x):
313 self.x = x
318 self.x = x
314 def bar(self, y):
319 def bar(self, y):
315 return self.x + y + 1
320 return self.x + y + 1
316 @property
321 @property
317 def quux(self):
322 def quux(self):
318 return 43
323 return 43
319
324
320 class Bar: # old-style class
325 class Bar: # old-style class
321 def foo(self):
326 def foo(self):
322 return 2
327 return 2
323 """)
328 """)
324
329
325 def check_module_contents():
330 def check_module_contents():
326 nt.assert_equal(mod.x, 10)
331 nt.assert_equal(mod.x, 10)
327 nt.assert_false(hasattr(mod, 'z'))
332 nt.assert_false(hasattr(mod, 'z'))
328
333
329 nt.assert_equal(old_foo(0), 4) # superreload magic!
334 nt.assert_equal(old_foo(0), 4) # superreload magic!
330 nt.assert_equal(mod.foo(0), 4)
335 nt.assert_equal(mod.foo(0), 4)
331
336
332 obj = mod.Baz(9)
337 obj = mod.Baz(9)
333 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
338 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
334 nt.assert_equal(obj.bar(1), 11)
339 nt.assert_equal(obj.bar(1), 11)
335
340
336 nt.assert_equal(old_obj.quux, 43)
341 nt.assert_equal(old_obj.quux, 43)
337 nt.assert_equal(obj.quux, 43)
342 nt.assert_equal(obj.quux, 43)
338
343
339 nt.assert_false(hasattr(old_obj, 'zzz'))
344 nt.assert_false(hasattr(old_obj, 'zzz'))
340 nt.assert_false(hasattr(obj, 'zzz'))
345 nt.assert_false(hasattr(obj, 'zzz'))
341
346
342 obj2 = mod.Bar()
347 obj2 = mod.Bar()
343 nt.assert_equal(old_obj2.foo(), 2)
348 nt.assert_equal(old_obj2.foo(), 2)
344 nt.assert_equal(obj2.foo(), 2)
349 nt.assert_equal(obj2.foo(), 2)
345
350
346 self.shell.run_code("pass") # trigger reload
351 self.shell.run_code("pass") # trigger reload
347 check_module_contents()
352 check_module_contents()
348
353
349 #
354 #
350 # Another failure case: deleted file (shouldn't reload)
355 # Another failure case: deleted file (shouldn't reload)
351 #
356 #
352 os.unlink(mod_fn)
357 os.unlink(mod_fn)
353
358
354 self.shell.run_code("pass") # trigger reload
359 self.shell.run_code("pass") # trigger reload
355 check_module_contents()
360 check_module_contents()
356
361
357 #
362 #
358 # Disable autoreload and rewrite module: no reload should occur
363 # Disable autoreload and rewrite module: no reload should occur
359 #
364 #
360 if use_aimport:
365 if use_aimport:
361 self.shell.magic_aimport("-" + mod_name)
366 self.shell.magic_aimport("-" + mod_name)
362 stream = StringIO()
367 stream = StringIO()
363 self.shell.magic_aimport("", stream=stream)
368 self.shell.magic_aimport("", stream=stream)
364 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
369 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
365 stream.getvalue())
370 stream.getvalue())
366
371
367 # This should succeed, although no such module exists
372 # This should succeed, although no such module exists
368 self.shell.magic_aimport("-tmpmod_as318989e89ds")
373 self.shell.magic_aimport("-tmpmod_as318989e89ds")
369 else:
374 else:
370 self.shell.magic_autoreload("0")
375 self.shell.magic_autoreload("0")
371
376
372 self.write_file(mod_fn, """
377 self.write_file(mod_fn, """
373 x = -99
378 x = -99
374 """)
379 """)
375
380
376 self.shell.run_code("pass") # trigger reload
381 self.shell.run_code("pass") # trigger reload
377 self.shell.run_code("pass")
382 self.shell.run_code("pass")
378 check_module_contents()
383 check_module_contents()
379
384
380 #
385 #
381 # Re-enable autoreload: reload should now occur
386 # Re-enable autoreload: reload should now occur
382 #
387 #
383 if use_aimport:
388 if use_aimport:
384 self.shell.magic_aimport(mod_name)
389 self.shell.magic_aimport(mod_name)
385 else:
390 else:
386 self.shell.magic_autoreload("")
391 self.shell.magic_autoreload("")
387
392
388 self.shell.run_code("pass") # trigger reload
393 self.shell.run_code("pass") # trigger reload
389 nt.assert_equal(mod.x, -99)
394 nt.assert_equal(mod.x, -99)
390
395
391 def test_smoketest_aimport(self):
396 def test_smoketest_aimport(self):
392 self._check_smoketest(use_aimport=True)
397 self._check_smoketest(use_aimport=True)
393
398
394 def test_smoketest_autoreload(self):
399 def test_smoketest_autoreload(self):
395 self._check_smoketest(use_aimport=False)
400 self._check_smoketest(use_aimport=False)
396
401
397
402
398
403
399
404
General Comments 0
You need to be logged in to leave comments. Login now