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