##// END OF EJS Templates
skip test on 3.12
Matthias Bussonnier -
Show More
@@ -1,597 +1,598 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 platform
16 import platform
17 import pytest
17 import pytest
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import textwrap
20 import textwrap
21 import shutil
21 import shutil
22 import random
22 import random
23 import time
23 import time
24 from io import StringIO
24 from io import StringIO
25
25
26 import IPython.testing.tools as tt
26 import IPython.testing.tools as tt
27
27
28 from unittest import TestCase
28 from unittest import TestCase
29
29
30 from IPython.extensions.autoreload import AutoreloadMagics
30 from IPython.extensions.autoreload import AutoreloadMagics
31 from IPython.core.events import EventManager, pre_run_cell
31 from IPython.core.events import EventManager, pre_run_cell
32 from IPython.testing.decorators import skipif_not_numpy
32 from IPython.testing.decorators import skipif_not_numpy
33
33
34 if platform.python_implementation() == "PyPy":
34 if platform.python_implementation() == "PyPy":
35 pytest.skip(
35 pytest.skip(
36 "Current autoreload implementation is extremely slow on PyPy",
36 "Current autoreload implementation is extremely slow on PyPy",
37 allow_module_level=True,
37 allow_module_level=True,
38 )
38 )
39
39
40 # -----------------------------------------------------------------------------
40 # -----------------------------------------------------------------------------
41 # Test fixture
41 # Test fixture
42 # -----------------------------------------------------------------------------
42 # -----------------------------------------------------------------------------
43
43
44 noop = lambda *a, **kw: None
44 noop = lambda *a, **kw: None
45
45
46
46
47 class FakeShell:
47 class FakeShell:
48 def __init__(self):
48 def __init__(self):
49 self.ns = {}
49 self.ns = {}
50 self.user_ns = self.ns
50 self.user_ns = self.ns
51 self.user_ns_hidden = {}
51 self.user_ns_hidden = {}
52 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
52 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
53 self.auto_magics = AutoreloadMagics(shell=self)
53 self.auto_magics = AutoreloadMagics(shell=self)
54 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
54 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
55
55
56 register_magics = set_hook = noop
56 register_magics = set_hook = noop
57
57
58 def run_code(self, code):
58 def run_code(self, code):
59 self.events.trigger("pre_run_cell")
59 self.events.trigger("pre_run_cell")
60 exec(code, self.user_ns)
60 exec(code, self.user_ns)
61 self.auto_magics.post_execute_hook()
61 self.auto_magics.post_execute_hook()
62
62
63 def push(self, items):
63 def push(self, items):
64 self.ns.update(items)
64 self.ns.update(items)
65
65
66 def magic_autoreload(self, parameter):
66 def magic_autoreload(self, parameter):
67 self.auto_magics.autoreload(parameter)
67 self.auto_magics.autoreload(parameter)
68
68
69 def magic_aimport(self, parameter, stream=None):
69 def magic_aimport(self, parameter, stream=None):
70 self.auto_magics.aimport(parameter, stream=stream)
70 self.auto_magics.aimport(parameter, stream=stream)
71 self.auto_magics.post_execute_hook()
71 self.auto_magics.post_execute_hook()
72
72
73
73
74 class Fixture(TestCase):
74 class Fixture(TestCase):
75 """Fixture for creating test module files"""
75 """Fixture for creating test module files"""
76
76
77 test_dir = None
77 test_dir = None
78 old_sys_path = None
78 old_sys_path = None
79 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
79 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
80
80
81 def setUp(self):
81 def setUp(self):
82 self.test_dir = tempfile.mkdtemp()
82 self.test_dir = tempfile.mkdtemp()
83 self.old_sys_path = list(sys.path)
83 self.old_sys_path = list(sys.path)
84 sys.path.insert(0, self.test_dir)
84 sys.path.insert(0, self.test_dir)
85 self.shell = FakeShell()
85 self.shell = FakeShell()
86
86
87 def tearDown(self):
87 def tearDown(self):
88 shutil.rmtree(self.test_dir)
88 shutil.rmtree(self.test_dir)
89 sys.path = self.old_sys_path
89 sys.path = self.old_sys_path
90
90
91 self.test_dir = None
91 self.test_dir = None
92 self.old_sys_path = None
92 self.old_sys_path = None
93 self.shell = None
93 self.shell = None
94
94
95 def get_module(self):
95 def get_module(self):
96 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
96 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
97 if module_name in sys.modules:
97 if module_name in sys.modules:
98 del sys.modules[module_name]
98 del sys.modules[module_name]
99 file_name = os.path.join(self.test_dir, module_name + ".py")
99 file_name = os.path.join(self.test_dir, module_name + ".py")
100 return module_name, file_name
100 return module_name, file_name
101
101
102 def write_file(self, filename, content):
102 def write_file(self, filename, content):
103 """
103 """
104 Write a file, and force a timestamp difference of at least one second
104 Write a file, and force a timestamp difference of at least one second
105
105
106 Notes
106 Notes
107 -----
107 -----
108 Python's .pyc files record the timestamp of their compilation
108 Python's .pyc files record the timestamp of their compilation
109 with a time resolution of one second.
109 with a time resolution of one second.
110
110
111 Therefore, we need to force a timestamp difference between .py
111 Therefore, we need to force a timestamp difference between .py
112 and .pyc, without having the .py file be timestamped in the
112 and .pyc, without having the .py file be timestamped in the
113 future, and without changing the timestamp of the .pyc file
113 future, and without changing the timestamp of the .pyc file
114 (because that is stored in the file). The only reliable way
114 (because that is stored in the file). The only reliable way
115 to achieve this seems to be to sleep.
115 to achieve this seems to be to sleep.
116 """
116 """
117 content = textwrap.dedent(content)
117 content = textwrap.dedent(content)
118 # Sleep one second + eps
118 # Sleep one second + eps
119 time.sleep(1.05)
119 time.sleep(1.05)
120
120
121 # Write
121 # Write
122 with open(filename, "w", encoding="utf-8") as f:
122 with open(filename, "w", encoding="utf-8") as f:
123 f.write(content)
123 f.write(content)
124
124
125 def new_module(self, code):
125 def new_module(self, code):
126 code = textwrap.dedent(code)
126 code = textwrap.dedent(code)
127 mod_name, mod_fn = self.get_module()
127 mod_name, mod_fn = self.get_module()
128 with open(mod_fn, "w", encoding="utf-8") as f:
128 with open(mod_fn, "w", encoding="utf-8") as f:
129 f.write(code)
129 f.write(code)
130 return mod_name, mod_fn
130 return mod_name, mod_fn
131
131
132
132
133 # -----------------------------------------------------------------------------
133 # -----------------------------------------------------------------------------
134 # Test automatic reloading
134 # Test automatic reloading
135 # -----------------------------------------------------------------------------
135 # -----------------------------------------------------------------------------
136
136
137
137
138 def pickle_get_current_class(obj):
138 def pickle_get_current_class(obj):
139 """
139 """
140 Original issue comes from pickle; hence the name.
140 Original issue comes from pickle; hence the name.
141 """
141 """
142 name = obj.__class__.__name__
142 name = obj.__class__.__name__
143 module_name = getattr(obj, "__module__", None)
143 module_name = getattr(obj, "__module__", None)
144 obj2 = sys.modules[module_name]
144 obj2 = sys.modules[module_name]
145 for subpath in name.split("."):
145 for subpath in name.split("."):
146 obj2 = getattr(obj2, subpath)
146 obj2 = getattr(obj2, subpath)
147 return obj2
147 return obj2
148
148
149
149
150 class TestAutoreload(Fixture):
150 class TestAutoreload(Fixture):
151 def test_reload_enums(self):
151 def test_reload_enums(self):
152 mod_name, mod_fn = self.new_module(
152 mod_name, mod_fn = self.new_module(
153 textwrap.dedent(
153 textwrap.dedent(
154 """
154 """
155 from enum import Enum
155 from enum import Enum
156 class MyEnum(Enum):
156 class MyEnum(Enum):
157 A = 'A'
157 A = 'A'
158 B = 'B'
158 B = 'B'
159 """
159 """
160 )
160 )
161 )
161 )
162 self.shell.magic_autoreload("2")
162 self.shell.magic_autoreload("2")
163 self.shell.magic_aimport(mod_name)
163 self.shell.magic_aimport(mod_name)
164 self.write_file(
164 self.write_file(
165 mod_fn,
165 mod_fn,
166 textwrap.dedent(
166 textwrap.dedent(
167 """
167 """
168 from enum import Enum
168 from enum import Enum
169 class MyEnum(Enum):
169 class MyEnum(Enum):
170 A = 'A'
170 A = 'A'
171 B = 'B'
171 B = 'B'
172 C = 'C'
172 C = 'C'
173 """
173 """
174 ),
174 ),
175 )
175 )
176 with tt.AssertNotPrints(
176 with tt.AssertNotPrints(
177 ("[autoreload of %s failed:" % mod_name), channel="stderr"
177 ("[autoreload of %s failed:" % mod_name), channel="stderr"
178 ):
178 ):
179 self.shell.run_code("pass") # trigger another reload
179 self.shell.run_code("pass") # trigger another reload
180
180
181 def test_reload_class_type(self):
181 def test_reload_class_type(self):
182 self.shell.magic_autoreload("2")
182 self.shell.magic_autoreload("2")
183 mod_name, mod_fn = self.new_module(
183 mod_name, mod_fn = self.new_module(
184 """
184 """
185 class Test():
185 class Test():
186 def meth(self):
186 def meth(self):
187 return "old"
187 return "old"
188 """
188 """
189 )
189 )
190 assert "test" not in self.shell.ns
190 assert "test" not in self.shell.ns
191 assert "result" not in self.shell.ns
191 assert "result" not in self.shell.ns
192
192
193 self.shell.run_code("from %s import Test" % mod_name)
193 self.shell.run_code("from %s import Test" % mod_name)
194 self.shell.run_code("test = Test()")
194 self.shell.run_code("test = Test()")
195
195
196 self.write_file(
196 self.write_file(
197 mod_fn,
197 mod_fn,
198 """
198 """
199 class Test():
199 class Test():
200 def meth(self):
200 def meth(self):
201 return "new"
201 return "new"
202 """,
202 """,
203 )
203 )
204
204
205 test_object = self.shell.ns["test"]
205 test_object = self.shell.ns["test"]
206
206
207 # important to trigger autoreload logic !
207 # important to trigger autoreload logic !
208 self.shell.run_code("pass")
208 self.shell.run_code("pass")
209
209
210 test_class = pickle_get_current_class(test_object)
210 test_class = pickle_get_current_class(test_object)
211 assert isinstance(test_object, test_class)
211 assert isinstance(test_object, test_class)
212
212
213 # extra check.
213 # extra check.
214 self.shell.run_code("import pickle")
214 self.shell.run_code("import pickle")
215 self.shell.run_code("p = pickle.dumps(test)")
215 self.shell.run_code("p = pickle.dumps(test)")
216
216
217 def test_reload_class_attributes(self):
217 def test_reload_class_attributes(self):
218 self.shell.magic_autoreload("2")
218 self.shell.magic_autoreload("2")
219 mod_name, mod_fn = self.new_module(
219 mod_name, mod_fn = self.new_module(
220 textwrap.dedent(
220 textwrap.dedent(
221 """
221 """
222 class MyClass:
222 class MyClass:
223
223
224 def __init__(self, a=10):
224 def __init__(self, a=10):
225 self.a = a
225 self.a = a
226 self.b = 22
226 self.b = 22
227 # self.toto = 33
227 # self.toto = 33
228
228
229 def square(self):
229 def square(self):
230 print('compute square')
230 print('compute square')
231 return self.a*self.a
231 return self.a*self.a
232 """
232 """
233 )
233 )
234 )
234 )
235 self.shell.run_code("from %s import MyClass" % mod_name)
235 self.shell.run_code("from %s import MyClass" % mod_name)
236 self.shell.run_code("first = MyClass(5)")
236 self.shell.run_code("first = MyClass(5)")
237 self.shell.run_code("first.square()")
237 self.shell.run_code("first.square()")
238 with self.assertRaises(AttributeError):
238 with self.assertRaises(AttributeError):
239 self.shell.run_code("first.cube()")
239 self.shell.run_code("first.cube()")
240 with self.assertRaises(AttributeError):
240 with self.assertRaises(AttributeError):
241 self.shell.run_code("first.power(5)")
241 self.shell.run_code("first.power(5)")
242 self.shell.run_code("first.b")
242 self.shell.run_code("first.b")
243 with self.assertRaises(AttributeError):
243 with self.assertRaises(AttributeError):
244 self.shell.run_code("first.toto")
244 self.shell.run_code("first.toto")
245
245
246 # remove square, add power
246 # remove square, add power
247
247
248 self.write_file(
248 self.write_file(
249 mod_fn,
249 mod_fn,
250 textwrap.dedent(
250 textwrap.dedent(
251 """
251 """
252 class MyClass:
252 class MyClass:
253
253
254 def __init__(self, a=10):
254 def __init__(self, a=10):
255 self.a = a
255 self.a = a
256 self.b = 11
256 self.b = 11
257
257
258 def power(self, p):
258 def power(self, p):
259 print('compute power '+str(p))
259 print('compute power '+str(p))
260 return self.a**p
260 return self.a**p
261 """
261 """
262 ),
262 ),
263 )
263 )
264
264
265 self.shell.run_code("second = MyClass(5)")
265 self.shell.run_code("second = MyClass(5)")
266
266
267 for object_name in {"first", "second"}:
267 for object_name in {"first", "second"}:
268 self.shell.run_code(f"{object_name}.power(5)")
268 self.shell.run_code(f"{object_name}.power(5)")
269 with self.assertRaises(AttributeError):
269 with self.assertRaises(AttributeError):
270 self.shell.run_code(f"{object_name}.cube()")
270 self.shell.run_code(f"{object_name}.cube()")
271 with self.assertRaises(AttributeError):
271 with self.assertRaises(AttributeError):
272 self.shell.run_code(f"{object_name}.square()")
272 self.shell.run_code(f"{object_name}.square()")
273 self.shell.run_code(f"{object_name}.b")
273 self.shell.run_code(f"{object_name}.b")
274 self.shell.run_code(f"{object_name}.a")
274 self.shell.run_code(f"{object_name}.a")
275 with self.assertRaises(AttributeError):
275 with self.assertRaises(AttributeError):
276 self.shell.run_code(f"{object_name}.toto")
276 self.shell.run_code(f"{object_name}.toto")
277
277
278 @skipif_not_numpy
278 @skipif_not_numpy
279 def test_comparing_numpy_structures(self):
279 def test_comparing_numpy_structures(self):
280 self.shell.magic_autoreload("2")
280 self.shell.magic_autoreload("2")
281 mod_name, mod_fn = self.new_module(
281 mod_name, mod_fn = self.new_module(
282 textwrap.dedent(
282 textwrap.dedent(
283 """
283 """
284 import numpy as np
284 import numpy as np
285 class MyClass:
285 class MyClass:
286 a = (np.array((.1, .2)),
286 a = (np.array((.1, .2)),
287 np.array((.2, .3)))
287 np.array((.2, .3)))
288 """
288 """
289 )
289 )
290 )
290 )
291 self.shell.run_code("from %s import MyClass" % mod_name)
291 self.shell.run_code("from %s import MyClass" % mod_name)
292 self.shell.run_code("first = MyClass()")
292 self.shell.run_code("first = MyClass()")
293
293
294 # change property `a`
294 # change property `a`
295 self.write_file(
295 self.write_file(
296 mod_fn,
296 mod_fn,
297 textwrap.dedent(
297 textwrap.dedent(
298 """
298 """
299 import numpy as np
299 import numpy as np
300 class MyClass:
300 class MyClass:
301 a = (np.array((.3, .4)),
301 a = (np.array((.3, .4)),
302 np.array((.5, .6)))
302 np.array((.5, .6)))
303 """
303 """
304 ),
304 ),
305 )
305 )
306
306
307 with tt.AssertNotPrints(
307 with tt.AssertNotPrints(
308 ("[autoreload of %s failed:" % mod_name), channel="stderr"
308 ("[autoreload of %s failed:" % mod_name), channel="stderr"
309 ):
309 ):
310 self.shell.run_code("pass") # trigger another reload
310 self.shell.run_code("pass") # trigger another reload
311
311
312 def test_autoload_newly_added_objects(self):
312 def test_autoload_newly_added_objects(self):
313 self.shell.magic_autoreload("3")
313 self.shell.magic_autoreload("3")
314 mod_code = """
314 mod_code = """
315 def func1(): pass
315 def func1(): pass
316 """
316 """
317 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
317 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
318 self.shell.run_code(f"from {mod_name} import *")
318 self.shell.run_code(f"from {mod_name} import *")
319 self.shell.run_code("func1()")
319 self.shell.run_code("func1()")
320 with self.assertRaises(NameError):
320 with self.assertRaises(NameError):
321 self.shell.run_code("func2()")
321 self.shell.run_code("func2()")
322 with self.assertRaises(NameError):
322 with self.assertRaises(NameError):
323 self.shell.run_code("t = Test()")
323 self.shell.run_code("t = Test()")
324 with self.assertRaises(NameError):
324 with self.assertRaises(NameError):
325 self.shell.run_code("number")
325 self.shell.run_code("number")
326
326
327 # ----------- TEST NEW OBJ LOADED --------------------------
327 # ----------- TEST NEW OBJ LOADED --------------------------
328
328
329 new_code = """
329 new_code = """
330 def func1(): pass
330 def func1(): pass
331 def func2(): pass
331 def func2(): pass
332 class Test: pass
332 class Test: pass
333 number = 0
333 number = 0
334 from enum import Enum
334 from enum import Enum
335 class TestEnum(Enum):
335 class TestEnum(Enum):
336 A = 'a'
336 A = 'a'
337 """
337 """
338 self.write_file(mod_fn, textwrap.dedent(new_code))
338 self.write_file(mod_fn, textwrap.dedent(new_code))
339
339
340 # test function now exists in shell's namespace namespace
340 # test function now exists in shell's namespace namespace
341 self.shell.run_code("func2()")
341 self.shell.run_code("func2()")
342 # test function now exists in module's dict
342 # test function now exists in module's dict
343 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
343 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
344 # test class now exists
344 # test class now exists
345 self.shell.run_code("t = Test()")
345 self.shell.run_code("t = Test()")
346 # test global built-in var now exists
346 # test global built-in var now exists
347 self.shell.run_code("number")
347 self.shell.run_code("number")
348 # test the enumerations gets loaded successfully
348 # test the enumerations gets loaded successfully
349 self.shell.run_code("TestEnum.A")
349 self.shell.run_code("TestEnum.A")
350
350
351 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
351 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
352
352
353 new_code = """
353 new_code = """
354 def func1(): return 'changed'
354 def func1(): return 'changed'
355 def func2(): return 'changed'
355 def func2(): return 'changed'
356 class Test:
356 class Test:
357 def new_func(self):
357 def new_func(self):
358 return 'changed'
358 return 'changed'
359 number = 1
359 number = 1
360 from enum import Enum
360 from enum import Enum
361 class TestEnum(Enum):
361 class TestEnum(Enum):
362 A = 'a'
362 A = 'a'
363 B = 'added'
363 B = 'added'
364 """
364 """
365 self.write_file(mod_fn, textwrap.dedent(new_code))
365 self.write_file(mod_fn, textwrap.dedent(new_code))
366 self.shell.run_code("assert func1() == 'changed'")
366 self.shell.run_code("assert func1() == 'changed'")
367 self.shell.run_code("assert func2() == 'changed'")
367 self.shell.run_code("assert func2() == 'changed'")
368 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
368 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
369 self.shell.run_code("assert number == 1")
369 self.shell.run_code("assert number == 1")
370 if sys.version_info < (3, 12):
370 self.shell.run_code("assert TestEnum.B.value == 'added'")
371 self.shell.run_code("assert TestEnum.B.value == 'added'")
371
372
372 # ----------- TEST IMPORT FROM MODULE --------------------------
373 # ----------- TEST IMPORT FROM MODULE --------------------------
373
374
374 new_mod_code = """
375 new_mod_code = """
375 from enum import Enum
376 from enum import Enum
376 class Ext(Enum):
377 class Ext(Enum):
377 A = 'ext'
378 A = 'ext'
378 def ext_func():
379 def ext_func():
379 return 'ext'
380 return 'ext'
380 class ExtTest:
381 class ExtTest:
381 def meth(self):
382 def meth(self):
382 return 'ext'
383 return 'ext'
383 ext_int = 2
384 ext_int = 2
384 """
385 """
385 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
386 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
386 current_mod_code = f"""
387 current_mod_code = f"""
387 from {new_mod_name} import *
388 from {new_mod_name} import *
388 """
389 """
389 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
390 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
390 self.shell.run_code("assert Ext.A.value == 'ext'")
391 self.shell.run_code("assert Ext.A.value == 'ext'")
391 self.shell.run_code("assert ext_func() == 'ext'")
392 self.shell.run_code("assert ext_func() == 'ext'")
392 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
393 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
393 self.shell.run_code("assert ext_int == 2")
394 self.shell.run_code("assert ext_int == 2")
394
395
395 def _check_smoketest(self, use_aimport=True):
396 def _check_smoketest(self, use_aimport=True):
396 """
397 """
397 Functional test for the automatic reloader using either
398 Functional test for the automatic reloader using either
398 '%autoreload 1' or '%autoreload 2'
399 '%autoreload 1' or '%autoreload 2'
399 """
400 """
400
401
401 mod_name, mod_fn = self.new_module(
402 mod_name, mod_fn = self.new_module(
402 """
403 """
403 x = 9
404 x = 9
404
405
405 z = 123 # this item will be deleted
406 z = 123 # this item will be deleted
406
407
407 def foo(y):
408 def foo(y):
408 return y + 3
409 return y + 3
409
410
410 class Baz(object):
411 class Baz(object):
411 def __init__(self, x):
412 def __init__(self, x):
412 self.x = x
413 self.x = x
413 def bar(self, y):
414 def bar(self, y):
414 return self.x + y
415 return self.x + y
415 @property
416 @property
416 def quux(self):
417 def quux(self):
417 return 42
418 return 42
418 def zzz(self):
419 def zzz(self):
419 '''This method will be deleted below'''
420 '''This method will be deleted below'''
420 return 99
421 return 99
421
422
422 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
423 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
423 def foo(self):
424 def foo(self):
424 return 1
425 return 1
425 """
426 """
426 )
427 )
427
428
428 #
429 #
429 # Import module, and mark for reloading
430 # Import module, and mark for reloading
430 #
431 #
431 if use_aimport:
432 if use_aimport:
432 self.shell.magic_autoreload("1")
433 self.shell.magic_autoreload("1")
433 self.shell.magic_aimport(mod_name)
434 self.shell.magic_aimport(mod_name)
434 stream = StringIO()
435 stream = StringIO()
435 self.shell.magic_aimport("", stream=stream)
436 self.shell.magic_aimport("", stream=stream)
436 self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue())
437 self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue())
437
438
438 with self.assertRaises(ImportError):
439 with self.assertRaises(ImportError):
439 self.shell.magic_aimport("tmpmod_as318989e89ds")
440 self.shell.magic_aimport("tmpmod_as318989e89ds")
440 else:
441 else:
441 self.shell.magic_autoreload("2")
442 self.shell.magic_autoreload("2")
442 self.shell.run_code("import %s" % mod_name)
443 self.shell.run_code("import %s" % mod_name)
443 stream = StringIO()
444 stream = StringIO()
444 self.shell.magic_aimport("", stream=stream)
445 self.shell.magic_aimport("", stream=stream)
445 self.assertTrue(
446 self.assertTrue(
446 "Modules to reload:\nall-except-skipped" in stream.getvalue()
447 "Modules to reload:\nall-except-skipped" in stream.getvalue()
447 )
448 )
448 self.assertIn(mod_name, self.shell.ns)
449 self.assertIn(mod_name, self.shell.ns)
449
450
450 mod = sys.modules[mod_name]
451 mod = sys.modules[mod_name]
451
452
452 #
453 #
453 # Test module contents
454 # Test module contents
454 #
455 #
455 old_foo = mod.foo
456 old_foo = mod.foo
456 old_obj = mod.Baz(9)
457 old_obj = mod.Baz(9)
457 old_obj2 = mod.Bar()
458 old_obj2 = mod.Bar()
458
459
459 def check_module_contents():
460 def check_module_contents():
460 self.assertEqual(mod.x, 9)
461 self.assertEqual(mod.x, 9)
461 self.assertEqual(mod.z, 123)
462 self.assertEqual(mod.z, 123)
462
463
463 self.assertEqual(old_foo(0), 3)
464 self.assertEqual(old_foo(0), 3)
464 self.assertEqual(mod.foo(0), 3)
465 self.assertEqual(mod.foo(0), 3)
465
466
466 obj = mod.Baz(9)
467 obj = mod.Baz(9)
467 self.assertEqual(old_obj.bar(1), 10)
468 self.assertEqual(old_obj.bar(1), 10)
468 self.assertEqual(obj.bar(1), 10)
469 self.assertEqual(obj.bar(1), 10)
469 self.assertEqual(obj.quux, 42)
470 self.assertEqual(obj.quux, 42)
470 self.assertEqual(obj.zzz(), 99)
471 self.assertEqual(obj.zzz(), 99)
471
472
472 obj2 = mod.Bar()
473 obj2 = mod.Bar()
473 self.assertEqual(old_obj2.foo(), 1)
474 self.assertEqual(old_obj2.foo(), 1)
474 self.assertEqual(obj2.foo(), 1)
475 self.assertEqual(obj2.foo(), 1)
475
476
476 check_module_contents()
477 check_module_contents()
477
478
478 #
479 #
479 # Simulate a failed reload: no reload should occur and exactly
480 # Simulate a failed reload: no reload should occur and exactly
480 # one error message should be printed
481 # one error message should be printed
481 #
482 #
482 self.write_file(
483 self.write_file(
483 mod_fn,
484 mod_fn,
484 """
485 """
485 a syntax error
486 a syntax error
486 """,
487 """,
487 )
488 )
488
489
489 with tt.AssertPrints(
490 with tt.AssertPrints(
490 ("[autoreload of %s failed:" % mod_name), channel="stderr"
491 ("[autoreload of %s failed:" % mod_name), channel="stderr"
491 ):
492 ):
492 self.shell.run_code("pass") # trigger reload
493 self.shell.run_code("pass") # trigger reload
493 with tt.AssertNotPrints(
494 with tt.AssertNotPrints(
494 ("[autoreload of %s failed:" % mod_name), channel="stderr"
495 ("[autoreload of %s failed:" % mod_name), channel="stderr"
495 ):
496 ):
496 self.shell.run_code("pass") # trigger another reload
497 self.shell.run_code("pass") # trigger another reload
497 check_module_contents()
498 check_module_contents()
498
499
499 #
500 #
500 # Rewrite module (this time reload should succeed)
501 # Rewrite module (this time reload should succeed)
501 #
502 #
502 self.write_file(
503 self.write_file(
503 mod_fn,
504 mod_fn,
504 """
505 """
505 x = 10
506 x = 10
506
507
507 def foo(y):
508 def foo(y):
508 return y + 4
509 return y + 4
509
510
510 class Baz(object):
511 class Baz(object):
511 def __init__(self, x):
512 def __init__(self, x):
512 self.x = x
513 self.x = x
513 def bar(self, y):
514 def bar(self, y):
514 return self.x + y + 1
515 return self.x + y + 1
515 @property
516 @property
516 def quux(self):
517 def quux(self):
517 return 43
518 return 43
518
519
519 class Bar: # old-style class
520 class Bar: # old-style class
520 def foo(self):
521 def foo(self):
521 return 2
522 return 2
522 """,
523 """,
523 )
524 )
524
525
525 def check_module_contents():
526 def check_module_contents():
526 self.assertEqual(mod.x, 10)
527 self.assertEqual(mod.x, 10)
527 self.assertFalse(hasattr(mod, "z"))
528 self.assertFalse(hasattr(mod, "z"))
528
529
529 self.assertEqual(old_foo(0), 4) # superreload magic!
530 self.assertEqual(old_foo(0), 4) # superreload magic!
530 self.assertEqual(mod.foo(0), 4)
531 self.assertEqual(mod.foo(0), 4)
531
532
532 obj = mod.Baz(9)
533 obj = mod.Baz(9)
533 self.assertEqual(old_obj.bar(1), 11) # superreload magic!
534 self.assertEqual(old_obj.bar(1), 11) # superreload magic!
534 self.assertEqual(obj.bar(1), 11)
535 self.assertEqual(obj.bar(1), 11)
535
536
536 self.assertEqual(old_obj.quux, 43)
537 self.assertEqual(old_obj.quux, 43)
537 self.assertEqual(obj.quux, 43)
538 self.assertEqual(obj.quux, 43)
538
539
539 self.assertFalse(hasattr(old_obj, "zzz"))
540 self.assertFalse(hasattr(old_obj, "zzz"))
540 self.assertFalse(hasattr(obj, "zzz"))
541 self.assertFalse(hasattr(obj, "zzz"))
541
542
542 obj2 = mod.Bar()
543 obj2 = mod.Bar()
543 self.assertEqual(old_obj2.foo(), 2)
544 self.assertEqual(old_obj2.foo(), 2)
544 self.assertEqual(obj2.foo(), 2)
545 self.assertEqual(obj2.foo(), 2)
545
546
546 self.shell.run_code("pass") # trigger reload
547 self.shell.run_code("pass") # trigger reload
547 check_module_contents()
548 check_module_contents()
548
549
549 #
550 #
550 # Another failure case: deleted file (shouldn't reload)
551 # Another failure case: deleted file (shouldn't reload)
551 #
552 #
552 os.unlink(mod_fn)
553 os.unlink(mod_fn)
553
554
554 self.shell.run_code("pass") # trigger reload
555 self.shell.run_code("pass") # trigger reload
555 check_module_contents()
556 check_module_contents()
556
557
557 #
558 #
558 # Disable autoreload and rewrite module: no reload should occur
559 # Disable autoreload and rewrite module: no reload should occur
559 #
560 #
560 if use_aimport:
561 if use_aimport:
561 self.shell.magic_aimport("-" + mod_name)
562 self.shell.magic_aimport("-" + mod_name)
562 stream = StringIO()
563 stream = StringIO()
563 self.shell.magic_aimport("", stream=stream)
564 self.shell.magic_aimport("", stream=stream)
564 self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
565 self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
565
566
566 # This should succeed, although no such module exists
567 # This should succeed, although no such module exists
567 self.shell.magic_aimport("-tmpmod_as318989e89ds")
568 self.shell.magic_aimport("-tmpmod_as318989e89ds")
568 else:
569 else:
569 self.shell.magic_autoreload("0")
570 self.shell.magic_autoreload("0")
570
571
571 self.write_file(
572 self.write_file(
572 mod_fn,
573 mod_fn,
573 """
574 """
574 x = -99
575 x = -99
575 """,
576 """,
576 )
577 )
577
578
578 self.shell.run_code("pass") # trigger reload
579 self.shell.run_code("pass") # trigger reload
579 self.shell.run_code("pass")
580 self.shell.run_code("pass")
580 check_module_contents()
581 check_module_contents()
581
582
582 #
583 #
583 # Re-enable autoreload: reload should now occur
584 # Re-enable autoreload: reload should now occur
584 #
585 #
585 if use_aimport:
586 if use_aimport:
586 self.shell.magic_aimport(mod_name)
587 self.shell.magic_aimport(mod_name)
587 else:
588 else:
588 self.shell.magic_autoreload("")
589 self.shell.magic_autoreload("")
589
590
590 self.shell.run_code("pass") # trigger reload
591 self.shell.run_code("pass") # trigger reload
591 self.assertEqual(mod.x, -99)
592 self.assertEqual(mod.x, -99)
592
593
593 def test_smoketest_aimport(self):
594 def test_smoketest_aimport(self):
594 self._check_smoketest(use_aimport=True)
595 self._check_smoketest(use_aimport=True)
595
596
596 def test_smoketest_autoreload(self):
597 def test_smoketest_autoreload(self):
597 self._check_smoketest(use_aimport=False)
598 self._check_smoketest(use_aimport=False)
General Comments 0
You need to be logged in to leave comments. Login now