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