##// END OF EJS Templates
Merge pull request #8243 from minrk/test-monkeypatch...
Thomas Kluyver -
r21118:1c4232df merge
parent child Browse files
Show More
@@ -1,512 +1,517
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for code execution (%run and related), which is particularly tricky.
2 """Tests for code execution (%run and related), which is particularly tricky.
3
3
4 Because of how %run manages namespaces, and the fact that we are trying here to
4 Because of how %run manages namespaces, and the fact that we are trying here to
5 verify subtle object deletion and reference counting issues, the %run tests
5 verify subtle object deletion and reference counting issues, the %run tests
6 will be kept in this separate file. This makes it easier to aggregate in one
6 will be kept in this separate file. This makes it easier to aggregate in one
7 place the tricks needed to handle it; most other magics are much easier to test
7 place the tricks needed to handle it; most other magics are much easier to test
8 and we do so in a common test_magic file.
8 and we do so in a common test_magic file.
9 """
9 """
10
10
11 # Copyright (c) IPython Development Team.
11 # Copyright (c) IPython Development Team.
12 # Distributed under the terms of the Modified BSD License.
12 # Distributed under the terms of the Modified BSD License.
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16
16
17 import functools
17 import functools
18 import os
18 import os
19 from os.path import join as pjoin
19 from os.path import join as pjoin
20 import random
20 import random
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23 import textwrap
23 import textwrap
24 import unittest
24 import unittest
25
25
26 try:
27 from unittest.mock import patch
28 except ImportError:
29 from mock import patch
30
26 import nose.tools as nt
31 import nose.tools as nt
27 from nose import SkipTest
32 from nose import SkipTest
28
33
29 from IPython.testing import decorators as dec
34 from IPython.testing import decorators as dec
30 from IPython.testing import tools as tt
35 from IPython.testing import tools as tt
31 from IPython.utils import py3compat
36 from IPython.utils import py3compat
32 from IPython.utils.io import capture_output
37 from IPython.utils.io import capture_output
33 from IPython.utils.tempdir import TemporaryDirectory
38 from IPython.utils.tempdir import TemporaryDirectory
34 from IPython.core import debugger
39 from IPython.core import debugger
35
40
36
41
37 def doctest_refbug():
42 def doctest_refbug():
38 """Very nasty problem with references held by multiple runs of a script.
43 """Very nasty problem with references held by multiple runs of a script.
39 See: https://github.com/ipython/ipython/issues/141
44 See: https://github.com/ipython/ipython/issues/141
40
45
41 In [1]: _ip.clear_main_mod_cache()
46 In [1]: _ip.clear_main_mod_cache()
42 # random
47 # random
43
48
44 In [2]: %run refbug
49 In [2]: %run refbug
45
50
46 In [3]: call_f()
51 In [3]: call_f()
47 lowercased: hello
52 lowercased: hello
48
53
49 In [4]: %run refbug
54 In [4]: %run refbug
50
55
51 In [5]: call_f()
56 In [5]: call_f()
52 lowercased: hello
57 lowercased: hello
53 lowercased: hello
58 lowercased: hello
54 """
59 """
55
60
56
61
57 def doctest_run_builtins():
62 def doctest_run_builtins():
58 r"""Check that %run doesn't damage __builtins__.
63 r"""Check that %run doesn't damage __builtins__.
59
64
60 In [1]: import tempfile
65 In [1]: import tempfile
61
66
62 In [2]: bid1 = id(__builtins__)
67 In [2]: bid1 = id(__builtins__)
63
68
64 In [3]: fname = tempfile.mkstemp('.py')[1]
69 In [3]: fname = tempfile.mkstemp('.py')[1]
65
70
66 In [3]: f = open(fname,'w')
71 In [3]: f = open(fname,'w')
67
72
68 In [4]: dummy= f.write('pass\n')
73 In [4]: dummy= f.write('pass\n')
69
74
70 In [5]: f.flush()
75 In [5]: f.flush()
71
76
72 In [6]: t1 = type(__builtins__)
77 In [6]: t1 = type(__builtins__)
73
78
74 In [7]: %run $fname
79 In [7]: %run $fname
75
80
76 In [7]: f.close()
81 In [7]: f.close()
77
82
78 In [8]: bid2 = id(__builtins__)
83 In [8]: bid2 = id(__builtins__)
79
84
80 In [9]: t2 = type(__builtins__)
85 In [9]: t2 = type(__builtins__)
81
86
82 In [10]: t1 == t2
87 In [10]: t1 == t2
83 Out[10]: True
88 Out[10]: True
84
89
85 In [10]: bid1 == bid2
90 In [10]: bid1 == bid2
86 Out[10]: True
91 Out[10]: True
87
92
88 In [12]: try:
93 In [12]: try:
89 ....: os.unlink(fname)
94 ....: os.unlink(fname)
90 ....: except:
95 ....: except:
91 ....: pass
96 ....: pass
92 ....:
97 ....:
93 """
98 """
94
99
95
100
96 def doctest_run_option_parser():
101 def doctest_run_option_parser():
97 r"""Test option parser in %run.
102 r"""Test option parser in %run.
98
103
99 In [1]: %run print_argv.py
104 In [1]: %run print_argv.py
100 []
105 []
101
106
102 In [2]: %run print_argv.py print*.py
107 In [2]: %run print_argv.py print*.py
103 ['print_argv.py']
108 ['print_argv.py']
104
109
105 In [3]: %run -G print_argv.py print*.py
110 In [3]: %run -G print_argv.py print*.py
106 ['print*.py']
111 ['print*.py']
107
112
108 """
113 """
109
114
110
115
111 @dec.skip_win32
116 @dec.skip_win32
112 def doctest_run_option_parser_for_posix():
117 def doctest_run_option_parser_for_posix():
113 r"""Test option parser in %run (Linux/OSX specific).
118 r"""Test option parser in %run (Linux/OSX specific).
114
119
115 You need double quote to escape glob in POSIX systems:
120 You need double quote to escape glob in POSIX systems:
116
121
117 In [1]: %run print_argv.py print\\*.py
122 In [1]: %run print_argv.py print\\*.py
118 ['print*.py']
123 ['print*.py']
119
124
120 You can't use quote to escape glob in POSIX systems:
125 You can't use quote to escape glob in POSIX systems:
121
126
122 In [2]: %run print_argv.py 'print*.py'
127 In [2]: %run print_argv.py 'print*.py'
123 ['print_argv.py']
128 ['print_argv.py']
124
129
125 """
130 """
126
131
127
132
128 @dec.skip_if_not_win32
133 @dec.skip_if_not_win32
129 def doctest_run_option_parser_for_windows():
134 def doctest_run_option_parser_for_windows():
130 r"""Test option parser in %run (Windows specific).
135 r"""Test option parser in %run (Windows specific).
131
136
132 In Windows, you can't escape ``*` `by backslash:
137 In Windows, you can't escape ``*` `by backslash:
133
138
134 In [1]: %run print_argv.py print\\*.py
139 In [1]: %run print_argv.py print\\*.py
135 ['print\\*.py']
140 ['print\\*.py']
136
141
137 You can use quote to escape glob:
142 You can use quote to escape glob:
138
143
139 In [2]: %run print_argv.py 'print*.py'
144 In [2]: %run print_argv.py 'print*.py'
140 ['print*.py']
145 ['print*.py']
141
146
142 """
147 """
143
148
144
149
145 @py3compat.doctest_refactor_print
150 @py3compat.doctest_refactor_print
146 def doctest_reset_del():
151 def doctest_reset_del():
147 """Test that resetting doesn't cause errors in __del__ methods.
152 """Test that resetting doesn't cause errors in __del__ methods.
148
153
149 In [2]: class A(object):
154 In [2]: class A(object):
150 ...: def __del__(self):
155 ...: def __del__(self):
151 ...: print str("Hi")
156 ...: print str("Hi")
152 ...:
157 ...:
153
158
154 In [3]: a = A()
159 In [3]: a = A()
155
160
156 In [4]: get_ipython().reset()
161 In [4]: get_ipython().reset()
157 Hi
162 Hi
158
163
159 In [5]: 1+1
164 In [5]: 1+1
160 Out[5]: 2
165 Out[5]: 2
161 """
166 """
162
167
163 # For some tests, it will be handy to organize them in a class with a common
168 # For some tests, it will be handy to organize them in a class with a common
164 # setup that makes a temp file
169 # setup that makes a temp file
165
170
166 class TestMagicRunPass(tt.TempFileMixin):
171 class TestMagicRunPass(tt.TempFileMixin):
167
172
168 def setup(self):
173 def setup(self):
169 """Make a valid python temp file."""
174 """Make a valid python temp file."""
170 self.mktmp('pass\n')
175 self.mktmp('pass\n')
171
176
172 def run_tmpfile(self):
177 def run_tmpfile(self):
173 _ip = get_ipython()
178 _ip = get_ipython()
174 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
179 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
175 # See below and ticket https://bugs.launchpad.net/bugs/366353
180 # See below and ticket https://bugs.launchpad.net/bugs/366353
176 _ip.magic('run %s' % self.fname)
181 _ip.magic('run %s' % self.fname)
177
182
178 def run_tmpfile_p(self):
183 def run_tmpfile_p(self):
179 _ip = get_ipython()
184 _ip = get_ipython()
180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
185 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
181 # See below and ticket https://bugs.launchpad.net/bugs/366353
186 # See below and ticket https://bugs.launchpad.net/bugs/366353
182 _ip.magic('run -p %s' % self.fname)
187 _ip.magic('run -p %s' % self.fname)
183
188
184 def test_builtins_id(self):
189 def test_builtins_id(self):
185 """Check that %run doesn't damage __builtins__ """
190 """Check that %run doesn't damage __builtins__ """
186 _ip = get_ipython()
191 _ip = get_ipython()
187 # Test that the id of __builtins__ is not modified by %run
192 # Test that the id of __builtins__ is not modified by %run
188 bid1 = id(_ip.user_ns['__builtins__'])
193 bid1 = id(_ip.user_ns['__builtins__'])
189 self.run_tmpfile()
194 self.run_tmpfile()
190 bid2 = id(_ip.user_ns['__builtins__'])
195 bid2 = id(_ip.user_ns['__builtins__'])
191 nt.assert_equal(bid1, bid2)
196 nt.assert_equal(bid1, bid2)
192
197
193 def test_builtins_type(self):
198 def test_builtins_type(self):
194 """Check that the type of __builtins__ doesn't change with %run.
199 """Check that the type of __builtins__ doesn't change with %run.
195
200
196 However, the above could pass if __builtins__ was already modified to
201 However, the above could pass if __builtins__ was already modified to
197 be a dict (it should be a module) by a previous use of %run. So we
202 be a dict (it should be a module) by a previous use of %run. So we
198 also check explicitly that it really is a module:
203 also check explicitly that it really is a module:
199 """
204 """
200 _ip = get_ipython()
205 _ip = get_ipython()
201 self.run_tmpfile()
206 self.run_tmpfile()
202 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
207 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
203
208
204 def test_prompts(self):
209 def test_prompts(self):
205 """Test that prompts correctly generate after %run"""
210 """Test that prompts correctly generate after %run"""
206 self.run_tmpfile()
211 self.run_tmpfile()
207 _ip = get_ipython()
212 _ip = get_ipython()
208 p2 = _ip.prompt_manager.render('in2').strip()
213 p2 = _ip.prompt_manager.render('in2').strip()
209 nt.assert_equal(p2[:3], '...')
214 nt.assert_equal(p2[:3], '...')
210
215
211 def test_run_profile( self ):
216 def test_run_profile( self ):
212 """Test that the option -p, which invokes the profiler, do not
217 """Test that the option -p, which invokes the profiler, do not
213 crash by invoking execfile"""
218 crash by invoking execfile"""
214 _ip = get_ipython()
219 _ip = get_ipython()
215 self.run_tmpfile_p()
220 self.run_tmpfile_p()
216
221
217
222
218 class TestMagicRunSimple(tt.TempFileMixin):
223 class TestMagicRunSimple(tt.TempFileMixin):
219
224
220 def test_simpledef(self):
225 def test_simpledef(self):
221 """Test that simple class definitions work."""
226 """Test that simple class definitions work."""
222 src = ("class foo: pass\n"
227 src = ("class foo: pass\n"
223 "def f(): return foo()")
228 "def f(): return foo()")
224 self.mktmp(src)
229 self.mktmp(src)
225 _ip.magic('run %s' % self.fname)
230 _ip.magic('run %s' % self.fname)
226 _ip.run_cell('t = isinstance(f(), foo)')
231 _ip.run_cell('t = isinstance(f(), foo)')
227 nt.assert_true(_ip.user_ns['t'])
232 nt.assert_true(_ip.user_ns['t'])
228
233
229 def test_obj_del(self):
234 def test_obj_del(self):
230 """Test that object's __del__ methods are called on exit."""
235 """Test that object's __del__ methods are called on exit."""
231 if sys.platform == 'win32':
236 if sys.platform == 'win32':
232 try:
237 try:
233 import win32api
238 import win32api
234 except ImportError:
239 except ImportError:
235 raise SkipTest("Test requires pywin32")
240 raise SkipTest("Test requires pywin32")
236 src = ("class A(object):\n"
241 src = ("class A(object):\n"
237 " def __del__(self):\n"
242 " def __del__(self):\n"
238 " print 'object A deleted'\n"
243 " print 'object A deleted'\n"
239 "a = A()\n")
244 "a = A()\n")
240 self.mktmp(py3compat.doctest_refactor_print(src))
245 self.mktmp(py3compat.doctest_refactor_print(src))
241 if dec.module_not_available('sqlite3'):
246 if dec.module_not_available('sqlite3'):
242 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
247 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
243 else:
248 else:
244 err = None
249 err = None
245 tt.ipexec_validate(self.fname, 'object A deleted', err)
250 tt.ipexec_validate(self.fname, 'object A deleted', err)
246
251
247 def test_aggressive_namespace_cleanup(self):
252 def test_aggressive_namespace_cleanup(self):
248 """Test that namespace cleanup is not too aggressive GH-238
253 """Test that namespace cleanup is not too aggressive GH-238
249
254
250 Returning from another run magic deletes the namespace"""
255 Returning from another run magic deletes the namespace"""
251 # see ticket https://github.com/ipython/ipython/issues/238
256 # see ticket https://github.com/ipython/ipython/issues/238
252 class secondtmp(tt.TempFileMixin): pass
257 class secondtmp(tt.TempFileMixin): pass
253 empty = secondtmp()
258 empty = secondtmp()
254 empty.mktmp('')
259 empty.mktmp('')
255 # On Windows, the filename will have \users in it, so we need to use the
260 # On Windows, the filename will have \users in it, so we need to use the
256 # repr so that the \u becomes \\u.
261 # repr so that the \u becomes \\u.
257 src = ("ip = get_ipython()\n"
262 src = ("ip = get_ipython()\n"
258 "for i in range(5):\n"
263 "for i in range(5):\n"
259 " try:\n"
264 " try:\n"
260 " ip.magic(%r)\n"
265 " ip.magic(%r)\n"
261 " except NameError as e:\n"
266 " except NameError as e:\n"
262 " print(i)\n"
267 " print(i)\n"
263 " break\n" % ('run ' + empty.fname))
268 " break\n" % ('run ' + empty.fname))
264 self.mktmp(src)
269 self.mktmp(src)
265 _ip.magic('run %s' % self.fname)
270 _ip.magic('run %s' % self.fname)
266 _ip.run_cell('ip == get_ipython()')
271 _ip.run_cell('ip == get_ipython()')
267 nt.assert_equal(_ip.user_ns['i'], 4)
272 nt.assert_equal(_ip.user_ns['i'], 4)
268
273
269 def test_run_second(self):
274 def test_run_second(self):
270 """Test that running a second file doesn't clobber the first, gh-3547
275 """Test that running a second file doesn't clobber the first, gh-3547
271 """
276 """
272 self.mktmp("avar = 1\n"
277 self.mktmp("avar = 1\n"
273 "def afunc():\n"
278 "def afunc():\n"
274 " return avar\n")
279 " return avar\n")
275
280
276 empty = tt.TempFileMixin()
281 empty = tt.TempFileMixin()
277 empty.mktmp("")
282 empty.mktmp("")
278
283
279 _ip.magic('run %s' % self.fname)
284 _ip.magic('run %s' % self.fname)
280 _ip.magic('run %s' % empty.fname)
285 _ip.magic('run %s' % empty.fname)
281 nt.assert_equal(_ip.user_ns['afunc'](), 1)
286 nt.assert_equal(_ip.user_ns['afunc'](), 1)
282
287
283 @dec.skip_win32
288 @dec.skip_win32
284 def test_tclass(self):
289 def test_tclass(self):
285 mydir = os.path.dirname(__file__)
290 mydir = os.path.dirname(__file__)
286 tc = os.path.join(mydir, 'tclass')
291 tc = os.path.join(mydir, 'tclass')
287 src = ("%%run '%s' C-first\n"
292 src = ("%%run '%s' C-first\n"
288 "%%run '%s' C-second\n"
293 "%%run '%s' C-second\n"
289 "%%run '%s' C-third\n") % (tc, tc, tc)
294 "%%run '%s' C-third\n") % (tc, tc, tc)
290 self.mktmp(src, '.ipy')
295 self.mktmp(src, '.ipy')
291 out = """\
296 out = """\
292 ARGV 1-: ['C-first']
297 ARGV 1-: ['C-first']
293 ARGV 1-: ['C-second']
298 ARGV 1-: ['C-second']
294 tclass.py: deleting object: C-first
299 tclass.py: deleting object: C-first
295 ARGV 1-: ['C-third']
300 ARGV 1-: ['C-third']
296 tclass.py: deleting object: C-second
301 tclass.py: deleting object: C-second
297 tclass.py: deleting object: C-third
302 tclass.py: deleting object: C-third
298 """
303 """
299 if dec.module_not_available('sqlite3'):
304 if dec.module_not_available('sqlite3'):
300 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
305 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
301 else:
306 else:
302 err = None
307 err = None
303 tt.ipexec_validate(self.fname, out, err)
308 tt.ipexec_validate(self.fname, out, err)
304
309
305 def test_run_i_after_reset(self):
310 def test_run_i_after_reset(self):
306 """Check that %run -i still works after %reset (gh-693)"""
311 """Check that %run -i still works after %reset (gh-693)"""
307 src = "yy = zz\n"
312 src = "yy = zz\n"
308 self.mktmp(src)
313 self.mktmp(src)
309 _ip.run_cell("zz = 23")
314 _ip.run_cell("zz = 23")
310 _ip.magic('run -i %s' % self.fname)
315 _ip.magic('run -i %s' % self.fname)
311 nt.assert_equal(_ip.user_ns['yy'], 23)
316 nt.assert_equal(_ip.user_ns['yy'], 23)
312 _ip.magic('reset -f')
317 _ip.magic('reset -f')
313 _ip.run_cell("zz = 23")
318 _ip.run_cell("zz = 23")
314 _ip.magic('run -i %s' % self.fname)
319 _ip.magic('run -i %s' % self.fname)
315 nt.assert_equal(_ip.user_ns['yy'], 23)
320 nt.assert_equal(_ip.user_ns['yy'], 23)
316
321
317 def test_unicode(self):
322 def test_unicode(self):
318 """Check that files in odd encodings are accepted."""
323 """Check that files in odd encodings are accepted."""
319 mydir = os.path.dirname(__file__)
324 mydir = os.path.dirname(__file__)
320 na = os.path.join(mydir, 'nonascii.py')
325 na = os.path.join(mydir, 'nonascii.py')
321 _ip.magic('run "%s"' % na)
326 _ip.magic('run "%s"' % na)
322 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
327 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
323
328
324 def test_run_py_file_attribute(self):
329 def test_run_py_file_attribute(self):
325 """Test handling of `__file__` attribute in `%run <file>.py`."""
330 """Test handling of `__file__` attribute in `%run <file>.py`."""
326 src = "t = __file__\n"
331 src = "t = __file__\n"
327 self.mktmp(src)
332 self.mktmp(src)
328 _missing = object()
333 _missing = object()
329 file1 = _ip.user_ns.get('__file__', _missing)
334 file1 = _ip.user_ns.get('__file__', _missing)
330 _ip.magic('run %s' % self.fname)
335 _ip.magic('run %s' % self.fname)
331 file2 = _ip.user_ns.get('__file__', _missing)
336 file2 = _ip.user_ns.get('__file__', _missing)
332
337
333 # Check that __file__ was equal to the filename in the script's
338 # Check that __file__ was equal to the filename in the script's
334 # namespace.
339 # namespace.
335 nt.assert_equal(_ip.user_ns['t'], self.fname)
340 nt.assert_equal(_ip.user_ns['t'], self.fname)
336
341
337 # Check that __file__ was not leaked back into user_ns.
342 # Check that __file__ was not leaked back into user_ns.
338 nt.assert_equal(file1, file2)
343 nt.assert_equal(file1, file2)
339
344
340 def test_run_ipy_file_attribute(self):
345 def test_run_ipy_file_attribute(self):
341 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
346 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
342 src = "t = __file__\n"
347 src = "t = __file__\n"
343 self.mktmp(src, ext='.ipy')
348 self.mktmp(src, ext='.ipy')
344 _missing = object()
349 _missing = object()
345 file1 = _ip.user_ns.get('__file__', _missing)
350 file1 = _ip.user_ns.get('__file__', _missing)
346 _ip.magic('run %s' % self.fname)
351 _ip.magic('run %s' % self.fname)
347 file2 = _ip.user_ns.get('__file__', _missing)
352 file2 = _ip.user_ns.get('__file__', _missing)
348
353
349 # Check that __file__ was equal to the filename in the script's
354 # Check that __file__ was equal to the filename in the script's
350 # namespace.
355 # namespace.
351 nt.assert_equal(_ip.user_ns['t'], self.fname)
356 nt.assert_equal(_ip.user_ns['t'], self.fname)
352
357
353 # Check that __file__ was not leaked back into user_ns.
358 # Check that __file__ was not leaked back into user_ns.
354 nt.assert_equal(file1, file2)
359 nt.assert_equal(file1, file2)
355
360
356 def test_run_formatting(self):
361 def test_run_formatting(self):
357 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
362 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
358 src = "pass"
363 src = "pass"
359 self.mktmp(src)
364 self.mktmp(src)
360 _ip.magic('run -t -N 1 %s' % self.fname)
365 _ip.magic('run -t -N 1 %s' % self.fname)
361 _ip.magic('run -t -N 10 %s' % self.fname)
366 _ip.magic('run -t -N 10 %s' % self.fname)
362
367
363 def test_ignore_sys_exit(self):
368 def test_ignore_sys_exit(self):
364 """Test the -e option to ignore sys.exit()"""
369 """Test the -e option to ignore sys.exit()"""
365 src = "import sys; sys.exit(1)"
370 src = "import sys; sys.exit(1)"
366 self.mktmp(src)
371 self.mktmp(src)
367 with tt.AssertPrints('SystemExit'):
372 with tt.AssertPrints('SystemExit'):
368 _ip.magic('run %s' % self.fname)
373 _ip.magic('run %s' % self.fname)
369
374
370 with tt.AssertNotPrints('SystemExit'):
375 with tt.AssertNotPrints('SystemExit'):
371 _ip.magic('run -e %s' % self.fname)
376 _ip.magic('run -e %s' % self.fname)
372
377
373 @dec.skip_without('IPython.nbformat') # Requires jsonschema
378 @dec.skip_without('IPython.nbformat') # Requires jsonschema
374 def test_run_nb(self):
379 def test_run_nb(self):
375 """Test %run notebook.ipynb"""
380 """Test %run notebook.ipynb"""
376 from IPython.nbformat import v4, writes
381 from IPython.nbformat import v4, writes
377 nb = v4.new_notebook(
382 nb = v4.new_notebook(
378 cells=[
383 cells=[
379 v4.new_markdown_cell("The Ultimate Question of Everything"),
384 v4.new_markdown_cell("The Ultimate Question of Everything"),
380 v4.new_code_cell("answer=42")
385 v4.new_code_cell("answer=42")
381 ]
386 ]
382 )
387 )
383 src = writes(nb, version=4)
388 src = writes(nb, version=4)
384 self.mktmp(src, ext='.ipynb')
389 self.mktmp(src, ext='.ipynb')
385
390
386 _ip.magic("run %s" % self.fname)
391 _ip.magic("run %s" % self.fname)
387
392
388 nt.assert_equal(_ip.user_ns['answer'], 42)
393 nt.assert_equal(_ip.user_ns['answer'], 42)
389
394
390
395
391
396
392 class TestMagicRunWithPackage(unittest.TestCase):
397 class TestMagicRunWithPackage(unittest.TestCase):
393
398
394 def writefile(self, name, content):
399 def writefile(self, name, content):
395 path = os.path.join(self.tempdir.name, name)
400 path = os.path.join(self.tempdir.name, name)
396 d = os.path.dirname(path)
401 d = os.path.dirname(path)
397 if not os.path.isdir(d):
402 if not os.path.isdir(d):
398 os.makedirs(d)
403 os.makedirs(d)
399 with open(path, 'w') as f:
404 with open(path, 'w') as f:
400 f.write(textwrap.dedent(content))
405 f.write(textwrap.dedent(content))
401
406
402 def setUp(self):
407 def setUp(self):
403 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
408 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
404 """Temporary valid python package name."""
409 """Temporary valid python package name."""
405
410
406 self.value = int(random.random() * 10000)
411 self.value = int(random.random() * 10000)
407
412
408 self.tempdir = TemporaryDirectory()
413 self.tempdir = TemporaryDirectory()
409 self.__orig_cwd = py3compat.getcwd()
414 self.__orig_cwd = py3compat.getcwd()
410 sys.path.insert(0, self.tempdir.name)
415 sys.path.insert(0, self.tempdir.name)
411
416
412 self.writefile(os.path.join(package, '__init__.py'), '')
417 self.writefile(os.path.join(package, '__init__.py'), '')
413 self.writefile(os.path.join(package, 'sub.py'), """
418 self.writefile(os.path.join(package, 'sub.py'), """
414 x = {0!r}
419 x = {0!r}
415 """.format(self.value))
420 """.format(self.value))
416 self.writefile(os.path.join(package, 'relative.py'), """
421 self.writefile(os.path.join(package, 'relative.py'), """
417 from .sub import x
422 from .sub import x
418 """)
423 """)
419 self.writefile(os.path.join(package, 'absolute.py'), """
424 self.writefile(os.path.join(package, 'absolute.py'), """
420 from {0}.sub import x
425 from {0}.sub import x
421 """.format(package))
426 """.format(package))
422
427
423 def tearDown(self):
428 def tearDown(self):
424 os.chdir(self.__orig_cwd)
429 os.chdir(self.__orig_cwd)
425 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
430 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
426 self.tempdir.cleanup()
431 self.tempdir.cleanup()
427
432
428 def check_run_submodule(self, submodule, opts=''):
433 def check_run_submodule(self, submodule, opts=''):
429 _ip.user_ns.pop('x', None)
434 _ip.user_ns.pop('x', None)
430 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
435 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
431 self.assertEqual(_ip.user_ns['x'], self.value,
436 self.assertEqual(_ip.user_ns['x'], self.value,
432 'Variable `x` is not loaded from module `{0}`.'
437 'Variable `x` is not loaded from module `{0}`.'
433 .format(submodule))
438 .format(submodule))
434
439
435 def test_run_submodule_with_absolute_import(self):
440 def test_run_submodule_with_absolute_import(self):
436 self.check_run_submodule('absolute')
441 self.check_run_submodule('absolute')
437
442
438 def test_run_submodule_with_relative_import(self):
443 def test_run_submodule_with_relative_import(self):
439 """Run submodule that has a relative import statement (#2727)."""
444 """Run submodule that has a relative import statement (#2727)."""
440 self.check_run_submodule('relative')
445 self.check_run_submodule('relative')
441
446
442 def test_prun_submodule_with_absolute_import(self):
447 def test_prun_submodule_with_absolute_import(self):
443 self.check_run_submodule('absolute', '-p')
448 self.check_run_submodule('absolute', '-p')
444
449
445 def test_prun_submodule_with_relative_import(self):
450 def test_prun_submodule_with_relative_import(self):
446 self.check_run_submodule('relative', '-p')
451 self.check_run_submodule('relative', '-p')
447
452
448 def with_fake_debugger(func):
453 def with_fake_debugger(func):
449 @functools.wraps(func)
454 @functools.wraps(func)
450 def wrapper(*args, **kwds):
455 def wrapper(*args, **kwds):
451 with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)):
456 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
452 return func(*args, **kwds)
457 return func(*args, **kwds)
453 return wrapper
458 return wrapper
454
459
455 @with_fake_debugger
460 @with_fake_debugger
456 def test_debug_run_submodule_with_absolute_import(self):
461 def test_debug_run_submodule_with_absolute_import(self):
457 self.check_run_submodule('absolute', '-d')
462 self.check_run_submodule('absolute', '-d')
458
463
459 @with_fake_debugger
464 @with_fake_debugger
460 def test_debug_run_submodule_with_relative_import(self):
465 def test_debug_run_submodule_with_relative_import(self):
461 self.check_run_submodule('relative', '-d')
466 self.check_run_submodule('relative', '-d')
462
467
463 def test_run__name__():
468 def test_run__name__():
464 with TemporaryDirectory() as td:
469 with TemporaryDirectory() as td:
465 path = pjoin(td, 'foo.py')
470 path = pjoin(td, 'foo.py')
466 with open(path, 'w') as f:
471 with open(path, 'w') as f:
467 f.write("q = __name__")
472 f.write("q = __name__")
468
473
469 _ip.user_ns.pop('q', None)
474 _ip.user_ns.pop('q', None)
470 _ip.magic('run {}'.format(path))
475 _ip.magic('run {}'.format(path))
471 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
476 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
472
477
473 _ip.magic('run -n {}'.format(path))
478 _ip.magic('run -n {}'.format(path))
474 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
479 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
475
480
476 def test_run_tb():
481 def test_run_tb():
477 """Test traceback offset in %run"""
482 """Test traceback offset in %run"""
478 with TemporaryDirectory() as td:
483 with TemporaryDirectory() as td:
479 path = pjoin(td, 'foo.py')
484 path = pjoin(td, 'foo.py')
480 with open(path, 'w') as f:
485 with open(path, 'w') as f:
481 f.write('\n'.join([
486 f.write('\n'.join([
482 "def foo():",
487 "def foo():",
483 " return bar()",
488 " return bar()",
484 "def bar():",
489 "def bar():",
485 " raise RuntimeError('hello!')",
490 " raise RuntimeError('hello!')",
486 "foo()",
491 "foo()",
487 ]))
492 ]))
488 with capture_output() as io:
493 with capture_output() as io:
489 _ip.magic('run {}'.format(path))
494 _ip.magic('run {}'.format(path))
490 out = io.stdout
495 out = io.stdout
491 nt.assert_not_in("execfile", out)
496 nt.assert_not_in("execfile", out)
492 nt.assert_in("RuntimeError", out)
497 nt.assert_in("RuntimeError", out)
493 nt.assert_equal(out.count("---->"), 3)
498 nt.assert_equal(out.count("---->"), 3)
494
499
495 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
500 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
496 def test_script_tb():
501 def test_script_tb():
497 """Test traceback offset in `ipython script.py`"""
502 """Test traceback offset in `ipython script.py`"""
498 with TemporaryDirectory() as td:
503 with TemporaryDirectory() as td:
499 path = pjoin(td, 'foo.py')
504 path = pjoin(td, 'foo.py')
500 with open(path, 'w') as f:
505 with open(path, 'w') as f:
501 f.write('\n'.join([
506 f.write('\n'.join([
502 "def foo():",
507 "def foo():",
503 " return bar()",
508 " return bar()",
504 "def bar():",
509 "def bar():",
505 " raise RuntimeError('hello!')",
510 " raise RuntimeError('hello!')",
506 "foo()",
511 "foo()",
507 ]))
512 ]))
508 out, err = tt.ipexec(path)
513 out, err = tt.ipexec(path)
509 nt.assert_not_in("execfile", out)
514 nt.assert_not_in("execfile", out)
510 nt.assert_in("RuntimeError", out)
515 nt.assert_in("RuntimeError", out)
511 nt.assert_equal(out.count("---->"), 3)
516 nt.assert_equal(out.count("---->"), 3)
512
517
@@ -1,691 +1,693
1 """Test interact and interactive."""
1 """Test interact and interactive."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import OrderedDict
8 try:
9 from unittest.mock import patch
10 except ImportError:
11 from mock import patch
9
12
10 import nose.tools as nt
13 import nose.tools as nt
11 import IPython.testing.tools as tt
12
14
13 from IPython.kernel.comm import Comm
15 from IPython.kernel.comm import Comm
14 from IPython.html import widgets
16 from IPython.html import widgets
15 from IPython.html.widgets import interact, interactive, Widget, interaction
17 from IPython.html.widgets import interact, interactive, Widget, interaction
16 from IPython.utils.py3compat import annotate
18 from IPython.utils.py3compat import annotate
17
19
18 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
19 # Utility stuff
21 # Utility stuff
20 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
21
23
22 class DummyComm(Comm):
24 class DummyComm(Comm):
23 comm_id = 'a-b-c-d'
25 comm_id = 'a-b-c-d'
24
26
25 def open(self, *args, **kwargs):
27 def open(self, *args, **kwargs):
26 pass
28 pass
27
29
28 def send(self, *args, **kwargs):
30 def send(self, *args, **kwargs):
29 pass
31 pass
30
32
31 def close(self, *args, **kwargs):
33 def close(self, *args, **kwargs):
32 pass
34 pass
33
35
34 _widget_attrs = {}
36 _widget_attrs = {}
35 displayed = []
37 displayed = []
36 undefined = object()
38 undefined = object()
37
39
38 def setup():
40 def setup():
39 _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
41 _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
40 Widget._comm_default = lambda self: DummyComm()
42 Widget._comm_default = lambda self: DummyComm()
41 _widget_attrs['_ipython_display_'] = Widget._ipython_display_
43 _widget_attrs['_ipython_display_'] = Widget._ipython_display_
42 def raise_not_implemented(*args, **kwargs):
44 def raise_not_implemented(*args, **kwargs):
43 raise NotImplementedError()
45 raise NotImplementedError()
44 Widget._ipython_display_ = raise_not_implemented
46 Widget._ipython_display_ = raise_not_implemented
45
47
46 def teardown():
48 def teardown():
47 for attr, value in _widget_attrs.items():
49 for attr, value in _widget_attrs.items():
48 if value is undefined:
50 if value is undefined:
49 delattr(Widget, attr)
51 delattr(Widget, attr)
50 else:
52 else:
51 setattr(Widget, attr, value)
53 setattr(Widget, attr, value)
52
54
53 def f(**kwargs):
55 def f(**kwargs):
54 pass
56 pass
55
57
56 def clear_display():
58 def clear_display():
57 global displayed
59 global displayed
58 displayed = []
60 displayed = []
59
61
60 def record_display(*args):
62 def record_display(*args):
61 displayed.extend(args)
63 displayed.extend(args)
62
64
63 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
64 # Actual tests
66 # Actual tests
65 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
66
68
67 def check_widget(w, **d):
69 def check_widget(w, **d):
68 """Check a single widget against a dict"""
70 """Check a single widget against a dict"""
69 for attr, expected in d.items():
71 for attr, expected in d.items():
70 if attr == 'cls':
72 if attr == 'cls':
71 nt.assert_is(w.__class__, expected)
73 nt.assert_is(w.__class__, expected)
72 else:
74 else:
73 value = getattr(w, attr)
75 value = getattr(w, attr)
74 nt.assert_equal(value, expected,
76 nt.assert_equal(value, expected,
75 "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
77 "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
76 )
78 )
77
79
78 def check_widgets(container, **to_check):
80 def check_widgets(container, **to_check):
79 """Check that widgets are created as expected"""
81 """Check that widgets are created as expected"""
80 # build a widget dictionary, so it matches
82 # build a widget dictionary, so it matches
81 widgets = {}
83 widgets = {}
82 for w in container.children:
84 for w in container.children:
83 widgets[w.description] = w
85 widgets[w.description] = w
84
86
85 for key, d in to_check.items():
87 for key, d in to_check.items():
86 nt.assert_in(key, widgets)
88 nt.assert_in(key, widgets)
87 check_widget(widgets[key], **d)
89 check_widget(widgets[key], **d)
88
90
89
91
90 def test_single_value_string():
92 def test_single_value_string():
91 a = u'hello'
93 a = u'hello'
92 c = interactive(f, a=a)
94 c = interactive(f, a=a)
93 w = c.children[0]
95 w = c.children[0]
94 check_widget(w,
96 check_widget(w,
95 cls=widgets.Text,
97 cls=widgets.Text,
96 description='a',
98 description='a',
97 value=a,
99 value=a,
98 )
100 )
99
101
100 def test_single_value_bool():
102 def test_single_value_bool():
101 for a in (True, False):
103 for a in (True, False):
102 c = interactive(f, a=a)
104 c = interactive(f, a=a)
103 w = c.children[0]
105 w = c.children[0]
104 check_widget(w,
106 check_widget(w,
105 cls=widgets.Checkbox,
107 cls=widgets.Checkbox,
106 description='a',
108 description='a',
107 value=a,
109 value=a,
108 )
110 )
109
111
110 def test_single_value_dict():
112 def test_single_value_dict():
111 for d in [
113 for d in [
112 dict(a=5),
114 dict(a=5),
113 dict(a=5, b='b', c=dict),
115 dict(a=5, b='b', c=dict),
114 ]:
116 ]:
115 c = interactive(f, d=d)
117 c = interactive(f, d=d)
116 w = c.children[0]
118 w = c.children[0]
117 check_widget(w,
119 check_widget(w,
118 cls=widgets.Dropdown,
120 cls=widgets.Dropdown,
119 description='d',
121 description='d',
120 options=d,
122 options=d,
121 value=next(iter(d.values())),
123 value=next(iter(d.values())),
122 )
124 )
123
125
124 def test_single_value_float():
126 def test_single_value_float():
125 for a in (2.25, 1.0, -3.5):
127 for a in (2.25, 1.0, -3.5):
126 c = interactive(f, a=a)
128 c = interactive(f, a=a)
127 w = c.children[0]
129 w = c.children[0]
128 check_widget(w,
130 check_widget(w,
129 cls=widgets.FloatSlider,
131 cls=widgets.FloatSlider,
130 description='a',
132 description='a',
131 value=a,
133 value=a,
132 min= -a if a > 0 else 3*a,
134 min= -a if a > 0 else 3*a,
133 max= 3*a if a > 0 else -a,
135 max= 3*a if a > 0 else -a,
134 step=0.1,
136 step=0.1,
135 readout=True,
137 readout=True,
136 )
138 )
137
139
138 def test_single_value_int():
140 def test_single_value_int():
139 for a in (1, 5, -3):
141 for a in (1, 5, -3):
140 c = interactive(f, a=a)
142 c = interactive(f, a=a)
141 nt.assert_equal(len(c.children), 1)
143 nt.assert_equal(len(c.children), 1)
142 w = c.children[0]
144 w = c.children[0]
143 check_widget(w,
145 check_widget(w,
144 cls=widgets.IntSlider,
146 cls=widgets.IntSlider,
145 description='a',
147 description='a',
146 value=a,
148 value=a,
147 min= -a if a > 0 else 3*a,
149 min= -a if a > 0 else 3*a,
148 max= 3*a if a > 0 else -a,
150 max= 3*a if a > 0 else -a,
149 step=1,
151 step=1,
150 readout=True,
152 readout=True,
151 )
153 )
152
154
153 def test_list_tuple_2_int():
155 def test_list_tuple_2_int():
154 with nt.assert_raises(ValueError):
156 with nt.assert_raises(ValueError):
155 c = interactive(f, tup=(1,1))
157 c = interactive(f, tup=(1,1))
156 with nt.assert_raises(ValueError):
158 with nt.assert_raises(ValueError):
157 c = interactive(f, tup=(1,-1))
159 c = interactive(f, tup=(1,-1))
158 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
160 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
159 c = interactive(f, tup=(min, max), lis=[min, max])
161 c = interactive(f, tup=(min, max), lis=[min, max])
160 nt.assert_equal(len(c.children), 2)
162 nt.assert_equal(len(c.children), 2)
161 d = dict(
163 d = dict(
162 cls=widgets.IntSlider,
164 cls=widgets.IntSlider,
163 min=min,
165 min=min,
164 max=max,
166 max=max,
165 step=1,
167 step=1,
166 readout=True,
168 readout=True,
167 )
169 )
168 check_widgets(c, tup=d, lis=d)
170 check_widgets(c, tup=d, lis=d)
169
171
170 def test_list_tuple_3_int():
172 def test_list_tuple_3_int():
171 with nt.assert_raises(ValueError):
173 with nt.assert_raises(ValueError):
172 c = interactive(f, tup=(1,2,0))
174 c = interactive(f, tup=(1,2,0))
173 with nt.assert_raises(ValueError):
175 with nt.assert_raises(ValueError):
174 c = interactive(f, tup=(1,2,-1))
176 c = interactive(f, tup=(1,2,-1))
175 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
177 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
176 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
178 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
177 nt.assert_equal(len(c.children), 2)
179 nt.assert_equal(len(c.children), 2)
178 d = dict(
180 d = dict(
179 cls=widgets.IntSlider,
181 cls=widgets.IntSlider,
180 min=min,
182 min=min,
181 max=max,
183 max=max,
182 step=step,
184 step=step,
183 readout=True,
185 readout=True,
184 )
186 )
185 check_widgets(c, tup=d, lis=d)
187 check_widgets(c, tup=d, lis=d)
186
188
187 def test_list_tuple_2_float():
189 def test_list_tuple_2_float():
188 with nt.assert_raises(ValueError):
190 with nt.assert_raises(ValueError):
189 c = interactive(f, tup=(1.0,1.0))
191 c = interactive(f, tup=(1.0,1.0))
190 with nt.assert_raises(ValueError):
192 with nt.assert_raises(ValueError):
191 c = interactive(f, tup=(0.5,-0.5))
193 c = interactive(f, tup=(0.5,-0.5))
192 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
194 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
193 c = interactive(f, tup=(min, max), lis=[min, max])
195 c = interactive(f, tup=(min, max), lis=[min, max])
194 nt.assert_equal(len(c.children), 2)
196 nt.assert_equal(len(c.children), 2)
195 d = dict(
197 d = dict(
196 cls=widgets.FloatSlider,
198 cls=widgets.FloatSlider,
197 min=min,
199 min=min,
198 max=max,
200 max=max,
199 step=.1,
201 step=.1,
200 readout=True,
202 readout=True,
201 )
203 )
202 check_widgets(c, tup=d, lis=d)
204 check_widgets(c, tup=d, lis=d)
203
205
204 def test_list_tuple_3_float():
206 def test_list_tuple_3_float():
205 with nt.assert_raises(ValueError):
207 with nt.assert_raises(ValueError):
206 c = interactive(f, tup=(1,2,0.0))
208 c = interactive(f, tup=(1,2,0.0))
207 with nt.assert_raises(ValueError):
209 with nt.assert_raises(ValueError):
208 c = interactive(f, tup=(-1,-2,1.))
210 c = interactive(f, tup=(-1,-2,1.))
209 with nt.assert_raises(ValueError):
211 with nt.assert_raises(ValueError):
210 c = interactive(f, tup=(1,2.,-1.))
212 c = interactive(f, tup=(1,2.,-1.))
211 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
213 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
212 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
214 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
213 nt.assert_equal(len(c.children), 2)
215 nt.assert_equal(len(c.children), 2)
214 d = dict(
216 d = dict(
215 cls=widgets.FloatSlider,
217 cls=widgets.FloatSlider,
216 min=min,
218 min=min,
217 max=max,
219 max=max,
218 step=step,
220 step=step,
219 readout=True,
221 readout=True,
220 )
222 )
221 check_widgets(c, tup=d, lis=d)
223 check_widgets(c, tup=d, lis=d)
222
224
223 def test_list_tuple_str():
225 def test_list_tuple_str():
224 values = ['hello', 'there', 'guy']
226 values = ['hello', 'there', 'guy']
225 first = values[0]
227 first = values[0]
226 c = interactive(f, tup=tuple(values), lis=list(values))
228 c = interactive(f, tup=tuple(values), lis=list(values))
227 nt.assert_equal(len(c.children), 2)
229 nt.assert_equal(len(c.children), 2)
228 d = dict(
230 d = dict(
229 cls=widgets.Dropdown,
231 cls=widgets.Dropdown,
230 value=first,
232 value=first,
231 options=values
233 options=values
232 )
234 )
233 check_widgets(c, tup=d, lis=d)
235 check_widgets(c, tup=d, lis=d)
234
236
235 def test_list_tuple_invalid():
237 def test_list_tuple_invalid():
236 for bad in [
238 for bad in [
237 (),
239 (),
238 (5, 'hi'),
240 (5, 'hi'),
239 ('hi', 5),
241 ('hi', 5),
240 ({},),
242 ({},),
241 (None,),
243 (None,),
242 ]:
244 ]:
243 with nt.assert_raises(ValueError):
245 with nt.assert_raises(ValueError):
244 print(bad) # because there is no custom message in assert_raises
246 print(bad) # because there is no custom message in assert_raises
245 c = interactive(f, tup=bad)
247 c = interactive(f, tup=bad)
246
248
247 def test_defaults():
249 def test_defaults():
248 @annotate(n=10)
250 @annotate(n=10)
249 def f(n, f=4.5, g=1):
251 def f(n, f=4.5, g=1):
250 pass
252 pass
251
253
252 c = interactive(f)
254 c = interactive(f)
253 check_widgets(c,
255 check_widgets(c,
254 n=dict(
256 n=dict(
255 cls=widgets.IntSlider,
257 cls=widgets.IntSlider,
256 value=10,
258 value=10,
257 ),
259 ),
258 f=dict(
260 f=dict(
259 cls=widgets.FloatSlider,
261 cls=widgets.FloatSlider,
260 value=4.5,
262 value=4.5,
261 ),
263 ),
262 g=dict(
264 g=dict(
263 cls=widgets.IntSlider,
265 cls=widgets.IntSlider,
264 value=1,
266 value=1,
265 ),
267 ),
266 )
268 )
267
269
268 def test_default_values():
270 def test_default_values():
269 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
271 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
270 def f(n, f=4.5, g=1, h=2, j='there'):
272 def f(n, f=4.5, g=1, h=2, j='there'):
271 pass
273 pass
272
274
273 c = interactive(f)
275 c = interactive(f)
274 check_widgets(c,
276 check_widgets(c,
275 n=dict(
277 n=dict(
276 cls=widgets.IntSlider,
278 cls=widgets.IntSlider,
277 value=10,
279 value=10,
278 ),
280 ),
279 f=dict(
281 f=dict(
280 cls=widgets.FloatSlider,
282 cls=widgets.FloatSlider,
281 value=4.5,
283 value=4.5,
282 ),
284 ),
283 g=dict(
285 g=dict(
284 cls=widgets.IntSlider,
286 cls=widgets.IntSlider,
285 value=5,
287 value=5,
286 ),
288 ),
287 h=dict(
289 h=dict(
288 cls=widgets.Dropdown,
290 cls=widgets.Dropdown,
289 options={'a': 1, 'b': 2},
291 options={'a': 1, 'b': 2},
290 value=2
292 value=2
291 ),
293 ),
292 j=dict(
294 j=dict(
293 cls=widgets.Dropdown,
295 cls=widgets.Dropdown,
294 options=['hi', 'there'],
296 options=['hi', 'there'],
295 value='there'
297 value='there'
296 ),
298 ),
297 )
299 )
298
300
299 def test_default_out_of_bounds():
301 def test_default_out_of_bounds():
300 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
302 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
301 def f(f='hi', h=5, j='other'):
303 def f(f='hi', h=5, j='other'):
302 pass
304 pass
303
305
304 c = interactive(f)
306 c = interactive(f)
305 check_widgets(c,
307 check_widgets(c,
306 f=dict(
308 f=dict(
307 cls=widgets.FloatSlider,
309 cls=widgets.FloatSlider,
308 value=5.,
310 value=5.,
309 ),
311 ),
310 h=dict(
312 h=dict(
311 cls=widgets.Dropdown,
313 cls=widgets.Dropdown,
312 options={'a': 1},
314 options={'a': 1},
313 value=1,
315 value=1,
314 ),
316 ),
315 j=dict(
317 j=dict(
316 cls=widgets.Dropdown,
318 cls=widgets.Dropdown,
317 options=['hi', 'there'],
319 options=['hi', 'there'],
318 value='hi',
320 value='hi',
319 ),
321 ),
320 )
322 )
321
323
322 def test_annotations():
324 def test_annotations():
323 @annotate(n=10, f=widgets.FloatText())
325 @annotate(n=10, f=widgets.FloatText())
324 def f(n, f):
326 def f(n, f):
325 pass
327 pass
326
328
327 c = interactive(f)
329 c = interactive(f)
328 check_widgets(c,
330 check_widgets(c,
329 n=dict(
331 n=dict(
330 cls=widgets.IntSlider,
332 cls=widgets.IntSlider,
331 value=10,
333 value=10,
332 ),
334 ),
333 f=dict(
335 f=dict(
334 cls=widgets.FloatText,
336 cls=widgets.FloatText,
335 ),
337 ),
336 )
338 )
337
339
338 def test_priority():
340 def test_priority():
339 @annotate(annotate='annotate', kwarg='annotate')
341 @annotate(annotate='annotate', kwarg='annotate')
340 def f(kwarg='default', annotate='default', default='default'):
342 def f(kwarg='default', annotate='default', default='default'):
341 pass
343 pass
342
344
343 c = interactive(f, kwarg='kwarg')
345 c = interactive(f, kwarg='kwarg')
344 check_widgets(c,
346 check_widgets(c,
345 kwarg=dict(
347 kwarg=dict(
346 cls=widgets.Text,
348 cls=widgets.Text,
347 value='kwarg',
349 value='kwarg',
348 ),
350 ),
349 annotate=dict(
351 annotate=dict(
350 cls=widgets.Text,
352 cls=widgets.Text,
351 value='annotate',
353 value='annotate',
352 ),
354 ),
353 )
355 )
354
356
355 @nt.with_setup(clear_display)
357 @nt.with_setup(clear_display)
356 def test_decorator_kwarg():
358 def test_decorator_kwarg():
357 with tt.monkeypatch(interaction, 'display', record_display):
359 with patch.object(interaction, 'display', record_display):
358 @interact(a=5)
360 @interact(a=5)
359 def foo(a):
361 def foo(a):
360 pass
362 pass
361 nt.assert_equal(len(displayed), 1)
363 nt.assert_equal(len(displayed), 1)
362 w = displayed[0].children[0]
364 w = displayed[0].children[0]
363 check_widget(w,
365 check_widget(w,
364 cls=widgets.IntSlider,
366 cls=widgets.IntSlider,
365 value=5,
367 value=5,
366 )
368 )
367
369
368 @nt.with_setup(clear_display)
370 @nt.with_setup(clear_display)
369 def test_interact_instancemethod():
371 def test_interact_instancemethod():
370 class Foo(object):
372 class Foo(object):
371 def show(self, x):
373 def show(self, x):
372 print(x)
374 print(x)
373
375
374 f = Foo()
376 f = Foo()
375
377
376 with tt.monkeypatch(interaction, 'display', record_display):
378 with patch.object(interaction, 'display', record_display):
377 g = interact(f.show, x=(1,10))
379 g = interact(f.show, x=(1,10))
378 nt.assert_equal(len(displayed), 1)
380 nt.assert_equal(len(displayed), 1)
379 w = displayed[0].children[0]
381 w = displayed[0].children[0]
380 check_widget(w,
382 check_widget(w,
381 cls=widgets.IntSlider,
383 cls=widgets.IntSlider,
382 value=5,
384 value=5,
383 )
385 )
384
386
385 @nt.with_setup(clear_display)
387 @nt.with_setup(clear_display)
386 def test_decorator_no_call():
388 def test_decorator_no_call():
387 with tt.monkeypatch(interaction, 'display', record_display):
389 with patch.object(interaction, 'display', record_display):
388 @interact
390 @interact
389 def foo(a='default'):
391 def foo(a='default'):
390 pass
392 pass
391 nt.assert_equal(len(displayed), 1)
393 nt.assert_equal(len(displayed), 1)
392 w = displayed[0].children[0]
394 w = displayed[0].children[0]
393 check_widget(w,
395 check_widget(w,
394 cls=widgets.Text,
396 cls=widgets.Text,
395 value='default',
397 value='default',
396 )
398 )
397
399
398 @nt.with_setup(clear_display)
400 @nt.with_setup(clear_display)
399 def test_call_interact():
401 def test_call_interact():
400 def foo(a='default'):
402 def foo(a='default'):
401 pass
403 pass
402 with tt.monkeypatch(interaction, 'display', record_display):
404 with patch.object(interaction, 'display', record_display):
403 ifoo = interact(foo)
405 ifoo = interact(foo)
404 nt.assert_equal(len(displayed), 1)
406 nt.assert_equal(len(displayed), 1)
405 w = displayed[0].children[0]
407 w = displayed[0].children[0]
406 check_widget(w,
408 check_widget(w,
407 cls=widgets.Text,
409 cls=widgets.Text,
408 value='default',
410 value='default',
409 )
411 )
410
412
411 @nt.with_setup(clear_display)
413 @nt.with_setup(clear_display)
412 def test_call_interact_kwargs():
414 def test_call_interact_kwargs():
413 def foo(a='default'):
415 def foo(a='default'):
414 pass
416 pass
415 with tt.monkeypatch(interaction, 'display', record_display):
417 with patch.object(interaction, 'display', record_display):
416 ifoo = interact(foo, a=10)
418 ifoo = interact(foo, a=10)
417 nt.assert_equal(len(displayed), 1)
419 nt.assert_equal(len(displayed), 1)
418 w = displayed[0].children[0]
420 w = displayed[0].children[0]
419 check_widget(w,
421 check_widget(w,
420 cls=widgets.IntSlider,
422 cls=widgets.IntSlider,
421 value=10,
423 value=10,
422 )
424 )
423
425
424 @nt.with_setup(clear_display)
426 @nt.with_setup(clear_display)
425 def test_call_decorated_on_trait_change():
427 def test_call_decorated_on_trait_change():
426 """test calling @interact decorated functions"""
428 """test calling @interact decorated functions"""
427 d = {}
429 d = {}
428 with tt.monkeypatch(interaction, 'display', record_display):
430 with patch.object(interaction, 'display', record_display):
429 @interact
431 @interact
430 def foo(a='default'):
432 def foo(a='default'):
431 d['a'] = a
433 d['a'] = a
432 return a
434 return a
433 nt.assert_equal(len(displayed), 1)
435 nt.assert_equal(len(displayed), 1)
434 w = displayed[0].children[0]
436 w = displayed[0].children[0]
435 check_widget(w,
437 check_widget(w,
436 cls=widgets.Text,
438 cls=widgets.Text,
437 value='default',
439 value='default',
438 )
440 )
439 # test calling the function directly
441 # test calling the function directly
440 a = foo('hello')
442 a = foo('hello')
441 nt.assert_equal(a, 'hello')
443 nt.assert_equal(a, 'hello')
442 nt.assert_equal(d['a'], 'hello')
444 nt.assert_equal(d['a'], 'hello')
443
445
444 # test that setting trait values calls the function
446 # test that setting trait values calls the function
445 w.value = 'called'
447 w.value = 'called'
446 nt.assert_equal(d['a'], 'called')
448 nt.assert_equal(d['a'], 'called')
447
449
448 @nt.with_setup(clear_display)
450 @nt.with_setup(clear_display)
449 def test_call_decorated_kwargs_on_trait_change():
451 def test_call_decorated_kwargs_on_trait_change():
450 """test calling @interact(foo=bar) decorated functions"""
452 """test calling @interact(foo=bar) decorated functions"""
451 d = {}
453 d = {}
452 with tt.monkeypatch(interaction, 'display', record_display):
454 with patch.object(interaction, 'display', record_display):
453 @interact(a='kwarg')
455 @interact(a='kwarg')
454 def foo(a='default'):
456 def foo(a='default'):
455 d['a'] = a
457 d['a'] = a
456 return a
458 return a
457 nt.assert_equal(len(displayed), 1)
459 nt.assert_equal(len(displayed), 1)
458 w = displayed[0].children[0]
460 w = displayed[0].children[0]
459 check_widget(w,
461 check_widget(w,
460 cls=widgets.Text,
462 cls=widgets.Text,
461 value='kwarg',
463 value='kwarg',
462 )
464 )
463 # test calling the function directly
465 # test calling the function directly
464 a = foo('hello')
466 a = foo('hello')
465 nt.assert_equal(a, 'hello')
467 nt.assert_equal(a, 'hello')
466 nt.assert_equal(d['a'], 'hello')
468 nt.assert_equal(d['a'], 'hello')
467
469
468 # test that setting trait values calls the function
470 # test that setting trait values calls the function
469 w.value = 'called'
471 w.value = 'called'
470 nt.assert_equal(d['a'], 'called')
472 nt.assert_equal(d['a'], 'called')
471
473
472 def test_fixed():
474 def test_fixed():
473 c = interactive(f, a=widgets.fixed(5), b='text')
475 c = interactive(f, a=widgets.fixed(5), b='text')
474 nt.assert_equal(len(c.children), 1)
476 nt.assert_equal(len(c.children), 1)
475 w = c.children[0]
477 w = c.children[0]
476 check_widget(w,
478 check_widget(w,
477 cls=widgets.Text,
479 cls=widgets.Text,
478 value='text',
480 value='text',
479 description='b',
481 description='b',
480 )
482 )
481
483
482 def test_default_description():
484 def test_default_description():
483 c = interactive(f, b='text')
485 c = interactive(f, b='text')
484 w = c.children[0]
486 w = c.children[0]
485 check_widget(w,
487 check_widget(w,
486 cls=widgets.Text,
488 cls=widgets.Text,
487 value='text',
489 value='text',
488 description='b',
490 description='b',
489 )
491 )
490
492
491 def test_custom_description():
493 def test_custom_description():
492 d = {}
494 d = {}
493 def record_kwargs(**kwargs):
495 def record_kwargs(**kwargs):
494 d.clear()
496 d.clear()
495 d.update(kwargs)
497 d.update(kwargs)
496
498
497 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
499 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
498 w = c.children[0]
500 w = c.children[0]
499 check_widget(w,
501 check_widget(w,
500 cls=widgets.Text,
502 cls=widgets.Text,
501 value='text',
503 value='text',
502 description='foo',
504 description='foo',
503 )
505 )
504 w.value = 'different text'
506 w.value = 'different text'
505 nt.assert_equal(d, {'b': 'different text'})
507 nt.assert_equal(d, {'b': 'different text'})
506
508
507 def test_interact_manual_button():
509 def test_interact_manual_button():
508 c = interactive(f, __manual=True)
510 c = interactive(f, __manual=True)
509 w = c.children[0]
511 w = c.children[0]
510 check_widget(w, cls=widgets.Button)
512 check_widget(w, cls=widgets.Button)
511
513
512 def test_interact_manual_nocall():
514 def test_interact_manual_nocall():
513 callcount = 0
515 callcount = 0
514 def calltest(testarg):
516 def calltest(testarg):
515 callcount += 1
517 callcount += 1
516 c = interactive(calltest, testarg=5, __manual=True)
518 c = interactive(calltest, testarg=5, __manual=True)
517 c.children[0].value = 10
519 c.children[0].value = 10
518 nt.assert_equal(callcount, 0)
520 nt.assert_equal(callcount, 0)
519
521
520 def test_int_range_logic():
522 def test_int_range_logic():
521 irsw = widgets.IntRangeSlider
523 irsw = widgets.IntRangeSlider
522 w = irsw(value=(2, 4), min=0, max=6)
524 w = irsw(value=(2, 4), min=0, max=6)
523 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
525 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
524 w.value = (4, 2)
526 w.value = (4, 2)
525 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
527 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
526 w.value = (-1, 7)
528 w.value = (-1, 7)
527 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
529 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
528 w.min = 3
530 w.min = 3
529 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
531 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
530 w.max = 3
532 w.max = 3
531 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
533 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
532
534
533 w.min = 0
535 w.min = 0
534 w.max = 6
536 w.max = 6
535 w.lower = 2
537 w.lower = 2
536 w.upper = 4
538 w.upper = 4
537 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
539 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
538 w.value = (0, 1) #lower non-overlapping range
540 w.value = (0, 1) #lower non-overlapping range
539 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
541 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
540 w.value = (5, 6) #upper non-overlapping range
542 w.value = (5, 6) #upper non-overlapping range
541 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
543 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
542 w.value = (-1, 4) #semi out-of-range
544 w.value = (-1, 4) #semi out-of-range
543 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
545 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
544 w.lower = 2
546 w.lower = 2
545 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
547 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
546 w.value = (-2, -1) #wholly out of range
548 w.value = (-2, -1) #wholly out of range
547 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
549 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
548 w.value = (7, 8)
550 w.value = (7, 8)
549 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
551 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
550
552
551 with nt.assert_raises(ValueError):
553 with nt.assert_raises(ValueError):
552 w.min = 7
554 w.min = 7
553 with nt.assert_raises(ValueError):
555 with nt.assert_raises(ValueError):
554 w.max = -1
556 w.max = -1
555 with nt.assert_raises(ValueError):
557 with nt.assert_raises(ValueError):
556 w.lower = 5
558 w.lower = 5
557 with nt.assert_raises(ValueError):
559 with nt.assert_raises(ValueError):
558 w.upper = 1
560 w.upper = 1
559
561
560 w = irsw(min=2, max=3)
562 w = irsw(min=2, max=3)
561 check_widget(w, min=2, max=3)
563 check_widget(w, min=2, max=3)
562 w = irsw(min=100, max=200)
564 w = irsw(min=100, max=200)
563 check_widget(w, lower=125, upper=175, value=(125, 175))
565 check_widget(w, lower=125, upper=175, value=(125, 175))
564
566
565 with nt.assert_raises(ValueError):
567 with nt.assert_raises(ValueError):
566 irsw(value=(2, 4), lower=3)
568 irsw(value=(2, 4), lower=3)
567 with nt.assert_raises(ValueError):
569 with nt.assert_raises(ValueError):
568 irsw(value=(2, 4), upper=3)
570 irsw(value=(2, 4), upper=3)
569 with nt.assert_raises(ValueError):
571 with nt.assert_raises(ValueError):
570 irsw(value=(2, 4), lower=3, upper=3)
572 irsw(value=(2, 4), lower=3, upper=3)
571 with nt.assert_raises(ValueError):
573 with nt.assert_raises(ValueError):
572 irsw(min=2, max=1)
574 irsw(min=2, max=1)
573 with nt.assert_raises(ValueError):
575 with nt.assert_raises(ValueError):
574 irsw(lower=5)
576 irsw(lower=5)
575 with nt.assert_raises(ValueError):
577 with nt.assert_raises(ValueError):
576 irsw(upper=5)
578 irsw(upper=5)
577
579
578
580
579 def test_float_range_logic():
581 def test_float_range_logic():
580 frsw = widgets.FloatRangeSlider
582 frsw = widgets.FloatRangeSlider
581 w = frsw(value=(.2, .4), min=0., max=.6)
583 w = frsw(value=(.2, .4), min=0., max=.6)
582 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
584 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
583 w.value = (.4, .2)
585 w.value = (.4, .2)
584 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
586 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
585 w.value = (-.1, .7)
587 w.value = (-.1, .7)
586 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
588 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
587 w.min = .3
589 w.min = .3
588 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
590 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
589 w.max = .3
591 w.max = .3
590 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
592 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
591
593
592 w.min = 0.
594 w.min = 0.
593 w.max = .6
595 w.max = .6
594 w.lower = .2
596 w.lower = .2
595 w.upper = .4
597 w.upper = .4
596 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
598 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
597 w.value = (0., .1) #lower non-overlapping range
599 w.value = (0., .1) #lower non-overlapping range
598 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
600 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
599 w.value = (.5, .6) #upper non-overlapping range
601 w.value = (.5, .6) #upper non-overlapping range
600 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
602 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
601 w.value = (-.1, .4) #semi out-of-range
603 w.value = (-.1, .4) #semi out-of-range
602 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
604 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
603 w.lower = .2
605 w.lower = .2
604 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
606 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
605 w.value = (-.2, -.1) #wholly out of range
607 w.value = (-.2, -.1) #wholly out of range
606 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
608 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
607 w.value = (.7, .8)
609 w.value = (.7, .8)
608 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
610 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
609
611
610 with nt.assert_raises(ValueError):
612 with nt.assert_raises(ValueError):
611 w.min = .7
613 w.min = .7
612 with nt.assert_raises(ValueError):
614 with nt.assert_raises(ValueError):
613 w.max = -.1
615 w.max = -.1
614 with nt.assert_raises(ValueError):
616 with nt.assert_raises(ValueError):
615 w.lower = .5
617 w.lower = .5
616 with nt.assert_raises(ValueError):
618 with nt.assert_raises(ValueError):
617 w.upper = .1
619 w.upper = .1
618
620
619 w = frsw(min=2, max=3)
621 w = frsw(min=2, max=3)
620 check_widget(w, min=2, max=3)
622 check_widget(w, min=2, max=3)
621 w = frsw(min=1., max=2.)
623 w = frsw(min=1., max=2.)
622 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
624 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
623
625
624 with nt.assert_raises(ValueError):
626 with nt.assert_raises(ValueError):
625 frsw(value=(2, 4), lower=3)
627 frsw(value=(2, 4), lower=3)
626 with nt.assert_raises(ValueError):
628 with nt.assert_raises(ValueError):
627 frsw(value=(2, 4), upper=3)
629 frsw(value=(2, 4), upper=3)
628 with nt.assert_raises(ValueError):
630 with nt.assert_raises(ValueError):
629 frsw(value=(2, 4), lower=3, upper=3)
631 frsw(value=(2, 4), lower=3, upper=3)
630 with nt.assert_raises(ValueError):
632 with nt.assert_raises(ValueError):
631 frsw(min=.2, max=.1)
633 frsw(min=.2, max=.1)
632 with nt.assert_raises(ValueError):
634 with nt.assert_raises(ValueError):
633 frsw(lower=5)
635 frsw(lower=5)
634 with nt.assert_raises(ValueError):
636 with nt.assert_raises(ValueError):
635 frsw(upper=5)
637 frsw(upper=5)
636
638
637
639
638 def test_multiple_selection():
640 def test_multiple_selection():
639 smw = widgets.SelectMultiple
641 smw = widgets.SelectMultiple
640
642
641 # degenerate multiple select
643 # degenerate multiple select
642 w = smw()
644 w = smw()
643 check_widget(w, value=tuple(), options=None, selected_labels=tuple())
645 check_widget(w, value=tuple(), options=None, selected_labels=tuple())
644
646
645 # don't accept random other value when no options
647 # don't accept random other value when no options
646 with nt.assert_raises(KeyError):
648 with nt.assert_raises(KeyError):
647 w.value = (2,)
649 w.value = (2,)
648 check_widget(w, value=tuple(), selected_labels=tuple())
650 check_widget(w, value=tuple(), selected_labels=tuple())
649
651
650 # basic multiple select
652 # basic multiple select
651 w = smw(options=[(1, 1)], value=[1])
653 w = smw(options=[(1, 1)], value=[1])
652 check_widget(w, cls=smw, value=(1,), options=[(1, 1)])
654 check_widget(w, cls=smw, value=(1,), options=[(1, 1)])
653
655
654 # don't accept random other value
656 # don't accept random other value
655 with nt.assert_raises(KeyError):
657 with nt.assert_raises(KeyError):
656 w.value = w.value + (2,)
658 w.value = w.value + (2,)
657 check_widget(w, value=(1,), selected_labels=(1,))
659 check_widget(w, value=(1,), selected_labels=(1,))
658
660
659 # change options
661 # change options
660 w.options = w.options + [(2, 2)]
662 w.options = w.options + [(2, 2)]
661 check_widget(w, options=[(1, 1), (2,2)])
663 check_widget(w, options=[(1, 1), (2,2)])
662
664
663 # change value
665 # change value
664 w.value = w.value + (2,)
666 w.value = w.value + (2,)
665 check_widget(w, value=(1, 2), selected_labels=(1, 2))
667 check_widget(w, value=(1, 2), selected_labels=(1, 2))
666
668
667 # change value name
669 # change value name
668 w.selected_labels = (1,)
670 w.selected_labels = (1,)
669 check_widget(w, value=(1,))
671 check_widget(w, value=(1,))
670
672
671 # don't accept random other names when no options
673 # don't accept random other names when no options
672 with nt.assert_raises(KeyError):
674 with nt.assert_raises(KeyError):
673 w.selected_labels = (3,)
675 w.selected_labels = (3,)
674 check_widget(w, value=(1,))
676 check_widget(w, value=(1,))
675
677
676 # don't accept selected_label (from superclass)
678 # don't accept selected_label (from superclass)
677 with nt.assert_raises(AttributeError):
679 with nt.assert_raises(AttributeError):
678 w.selected_label = 3
680 w.selected_label = 3
679
681
680 # don't return selected_label (from superclass)
682 # don't return selected_label (from superclass)
681 with nt.assert_raises(AttributeError):
683 with nt.assert_raises(AttributeError):
682 print(w.selected_label)
684 print(w.selected_label)
683
685
684 # dict style
686 # dict style
685 w.options = {1: 1}
687 w.options = {1: 1}
686 check_widget(w, options={1: 1})
688 check_widget(w, options={1: 1})
687
689
688 # updating
690 # updating
689 with nt.assert_raises(KeyError):
691 with nt.assert_raises(KeyError):
690 w.value = (2,)
692 w.value = (2,)
691 check_widget(w, options={1: 1})
693 check_widget(w, options={1: 1})
@@ -1,139 +1,139
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
5 # Copyright (C) 2008-2011 The IPython Development Team
5 # Distributed under the terms of the Modified BSD License.
6 #
6
7 # Distributed under the terms of the BSD License. The full license is in
7 try:
8 # the file COPYING, distributed as part of this software.
8 from unittest.mock import patch
9 #-----------------------------------------------------------------------------
9 except ImportError:
10 from mock import patch
10
11
11 import nose.tools as nt
12 import nose.tools as nt
12
13
13 from IPython.lib import latextools
14 from IPython.lib import latextools
14 from IPython.testing.decorators import onlyif_cmds_exist, skipif_not_matplotlib
15 from IPython.testing.decorators import onlyif_cmds_exist, skipif_not_matplotlib
15 from IPython.testing.tools import monkeypatch
16 from IPython.utils.process import FindCmdError
16 from IPython.utils.process import FindCmdError
17
17
18
18
19 def test_latex_to_png_dvipng_fails_when_no_cmd():
19 def test_latex_to_png_dvipng_fails_when_no_cmd():
20 """
20 """
21 `latex_to_png_dvipng` should return None when there is no required command
21 `latex_to_png_dvipng` should return None when there is no required command
22 """
22 """
23 for command in ['latex', 'dvipng']:
23 for command in ['latex', 'dvipng']:
24 yield (check_latex_to_png_dvipng_fails_when_no_cmd, command)
24 yield (check_latex_to_png_dvipng_fails_when_no_cmd, command)
25
25
26
26
27 def check_latex_to_png_dvipng_fails_when_no_cmd(command):
27 def check_latex_to_png_dvipng_fails_when_no_cmd(command):
28 def mock_find_cmd(arg):
28 def mock_find_cmd(arg):
29 if arg == command:
29 if arg == command:
30 raise FindCmdError
30 raise FindCmdError
31
31
32 with monkeypatch(latextools, "find_cmd", mock_find_cmd):
32 with patch.object(latextools, "find_cmd", mock_find_cmd):
33 nt.assert_equals(latextools.latex_to_png_dvipng("whatever", True),
33 nt.assert_equals(latextools.latex_to_png_dvipng("whatever", True),
34 None)
34 None)
35
35
36
36
37 @onlyif_cmds_exist('latex', 'dvipng')
37 @onlyif_cmds_exist('latex', 'dvipng')
38 def test_latex_to_png_dvipng_runs():
38 def test_latex_to_png_dvipng_runs():
39 """
39 """
40 Test that latex_to_png_dvipng just runs without error.
40 Test that latex_to_png_dvipng just runs without error.
41 """
41 """
42 def mock_kpsewhich(filename):
42 def mock_kpsewhich(filename):
43 nt.assert_equals(filename, "breqn.sty")
43 nt.assert_equals(filename, "breqn.sty")
44 return None
44 return None
45
45
46 for (s, wrap) in [(u"$$x^2$$", False), (u"x^2", True)]:
46 for (s, wrap) in [(u"$$x^2$$", False), (u"x^2", True)]:
47 yield (latextools.latex_to_png_dvipng, s, wrap)
47 yield (latextools.latex_to_png_dvipng, s, wrap)
48
48
49 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
49 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
50 yield (latextools.latex_to_png_dvipng, s, wrap)
50 yield (latextools.latex_to_png_dvipng, s, wrap)
51
51
52 @skipif_not_matplotlib
52 @skipif_not_matplotlib
53 def test_latex_to_png_mpl_runs():
53 def test_latex_to_png_mpl_runs():
54 """
54 """
55 Test that latex_to_png_mpl just runs without error.
55 Test that latex_to_png_mpl just runs without error.
56 """
56 """
57 def mock_kpsewhich(filename):
57 def mock_kpsewhich(filename):
58 nt.assert_equals(filename, "breqn.sty")
58 nt.assert_equals(filename, "breqn.sty")
59 return None
59 return None
60
60
61 for (s, wrap) in [("$x^2$", False), ("x^2", True)]:
61 for (s, wrap) in [("$x^2$", False), ("x^2", True)]:
62 yield (latextools.latex_to_png_mpl, s, wrap)
62 yield (latextools.latex_to_png_mpl, s, wrap)
63
63
64 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
64 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
65 yield (latextools.latex_to_png_mpl, s, wrap)
65 yield (latextools.latex_to_png_mpl, s, wrap)
66
66
67 @skipif_not_matplotlib
67 @skipif_not_matplotlib
68 def test_latex_to_html():
68 def test_latex_to_html():
69 img = latextools.latex_to_html("$x^2$")
69 img = latextools.latex_to_html("$x^2$")
70 nt.assert_in("data:image/png;base64,iVBOR", img)
70 nt.assert_in("data:image/png;base64,iVBOR", img)
71
71
72
72
73 def test_genelatex_no_wrap():
73 def test_genelatex_no_wrap():
74 """
74 """
75 Test genelatex with wrap=False.
75 Test genelatex with wrap=False.
76 """
76 """
77 def mock_kpsewhich(filename):
77 def mock_kpsewhich(filename):
78 assert False, ("kpsewhich should not be called "
78 assert False, ("kpsewhich should not be called "
79 "(called with {0})".format(filename))
79 "(called with {0})".format(filename))
80
80
81 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
81 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
82 nt.assert_equals(
82 nt.assert_equals(
83 '\n'.join(latextools.genelatex("body text", False)),
83 '\n'.join(latextools.genelatex("body text", False)),
84 r'''\documentclass{article}
84 r'''\documentclass{article}
85 \usepackage{amsmath}
85 \usepackage{amsmath}
86 \usepackage{amsthm}
86 \usepackage{amsthm}
87 \usepackage{amssymb}
87 \usepackage{amssymb}
88 \usepackage{bm}
88 \usepackage{bm}
89 \pagestyle{empty}
89 \pagestyle{empty}
90 \begin{document}
90 \begin{document}
91 body text
91 body text
92 \end{document}''')
92 \end{document}''')
93
93
94
94
95 def test_genelatex_wrap_with_breqn():
95 def test_genelatex_wrap_with_breqn():
96 """
96 """
97 Test genelatex with wrap=True for the case breqn.sty is installed.
97 Test genelatex with wrap=True for the case breqn.sty is installed.
98 """
98 """
99 def mock_kpsewhich(filename):
99 def mock_kpsewhich(filename):
100 nt.assert_equals(filename, "breqn.sty")
100 nt.assert_equals(filename, "breqn.sty")
101 return "path/to/breqn.sty"
101 return "path/to/breqn.sty"
102
102
103 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
103 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
104 nt.assert_equals(
104 nt.assert_equals(
105 '\n'.join(latextools.genelatex("x^2", True)),
105 '\n'.join(latextools.genelatex("x^2", True)),
106 r'''\documentclass{article}
106 r'''\documentclass{article}
107 \usepackage{amsmath}
107 \usepackage{amsmath}
108 \usepackage{amsthm}
108 \usepackage{amsthm}
109 \usepackage{amssymb}
109 \usepackage{amssymb}
110 \usepackage{bm}
110 \usepackage{bm}
111 \usepackage{breqn}
111 \usepackage{breqn}
112 \pagestyle{empty}
112 \pagestyle{empty}
113 \begin{document}
113 \begin{document}
114 \begin{dmath*}
114 \begin{dmath*}
115 x^2
115 x^2
116 \end{dmath*}
116 \end{dmath*}
117 \end{document}''')
117 \end{document}''')
118
118
119
119
120 def test_genelatex_wrap_without_breqn():
120 def test_genelatex_wrap_without_breqn():
121 """
121 """
122 Test genelatex with wrap=True for the case breqn.sty is not installed.
122 Test genelatex with wrap=True for the case breqn.sty is not installed.
123 """
123 """
124 def mock_kpsewhich(filename):
124 def mock_kpsewhich(filename):
125 nt.assert_equals(filename, "breqn.sty")
125 nt.assert_equals(filename, "breqn.sty")
126 return None
126 return None
127
127
128 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
128 with patch.object(latextools, "kpsewhich", mock_kpsewhich):
129 nt.assert_equals(
129 nt.assert_equals(
130 '\n'.join(latextools.genelatex("x^2", True)),
130 '\n'.join(latextools.genelatex("x^2", True)),
131 r'''\documentclass{article}
131 r'''\documentclass{article}
132 \usepackage{amsmath}
132 \usepackage{amsmath}
133 \usepackage{amsthm}
133 \usepackage{amsthm}
134 \usepackage{amssymb}
134 \usepackage{amssymb}
135 \usepackage{bm}
135 \usepackage{bm}
136 \pagestyle{empty}
136 \pagestyle{empty}
137 \begin{document}
137 \begin{document}
138 $$x^2$$
138 $$x^2$$
139 \end{document}''')
139 \end{document}''')
@@ -1,498 +1,487
1 """Generic testing tools.
1 """Generic testing tools.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 """
6 """
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2009 The IPython Development Team
11 # Copyright (C) 2009 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25
25
26 from contextlib import contextmanager
26 from contextlib import contextmanager
27 from io import StringIO
27 from io import StringIO
28 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
29
29
30 try:
30 try:
31 # These tools are used by parts of the runtime, so we make the nose
31 # These tools are used by parts of the runtime, so we make the nose
32 # dependency optional at this point. Nose is a hard dependency to run the
32 # dependency optional at this point. Nose is a hard dependency to run the
33 # test suite, but NOT to use ipython itself.
33 # test suite, but NOT to use ipython itself.
34 import nose.tools as nt
34 import nose.tools as nt
35 has_nose = True
35 has_nose = True
36 except ImportError:
36 except ImportError:
37 has_nose = False
37 has_nose = False
38
38
39 from IPython.config.loader import Config
39 from IPython.config.loader import Config
40 from IPython.utils.process import get_output_error_code
40 from IPython.utils.process import get_output_error_code
41 from IPython.utils.text import list_strings
41 from IPython.utils.text import list_strings
42 from IPython.utils.io import temp_pyfile, Tee
42 from IPython.utils.io import temp_pyfile, Tee
43 from IPython.utils import py3compat
43 from IPython.utils import py3compat
44 from IPython.utils.encoding import DEFAULT_ENCODING
44 from IPython.utils.encoding import DEFAULT_ENCODING
45
45
46 from . import decorators as dec
46 from . import decorators as dec
47 from . import skipdoctest
47 from . import skipdoctest
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Functions and classes
50 # Functions and classes
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 # The docstring for full_path doctests differently on win32 (different path
53 # The docstring for full_path doctests differently on win32 (different path
54 # separator) so just skip the doctest there. The example remains informative.
54 # separator) so just skip the doctest there. The example remains informative.
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
56
56
57 @doctest_deco
57 @doctest_deco
58 def full_path(startPath,files):
58 def full_path(startPath,files):
59 """Make full paths for all the listed files, based on startPath.
59 """Make full paths for all the listed files, based on startPath.
60
60
61 Only the base part of startPath is kept, since this routine is typically
61 Only the base part of startPath is kept, since this routine is typically
62 used with a script's ``__file__`` variable as startPath. The base of startPath
62 used with a script's ``__file__`` variable as startPath. The base of startPath
63 is then prepended to all the listed files, forming the output list.
63 is then prepended to all the listed files, forming the output list.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 startPath : string
67 startPath : string
68 Initial path to use as the base for the results. This path is split
68 Initial path to use as the base for the results. This path is split
69 using os.path.split() and only its first component is kept.
69 using os.path.split() and only its first component is kept.
70
70
71 files : string or list
71 files : string or list
72 One or more files.
72 One or more files.
73
73
74 Examples
74 Examples
75 --------
75 --------
76
76
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 ['/foo/a.txt', '/foo/b.txt']
78 ['/foo/a.txt', '/foo/b.txt']
79
79
80 >>> full_path('/foo',['a.txt','b.txt'])
80 >>> full_path('/foo',['a.txt','b.txt'])
81 ['/a.txt', '/b.txt']
81 ['/a.txt', '/b.txt']
82
82
83 If a single file is given, the output is still a list::
83 If a single file is given, the output is still a list::
84
84
85 >>> full_path('/foo','a.txt')
85 >>> full_path('/foo','a.txt')
86 ['/a.txt']
86 ['/a.txt']
87 """
87 """
88
88
89 files = list_strings(files)
89 files = list_strings(files)
90 base = os.path.split(startPath)[0]
90 base = os.path.split(startPath)[0]
91 return [ os.path.join(base,f) for f in files ]
91 return [ os.path.join(base,f) for f in files ]
92
92
93
93
94 def parse_test_output(txt):
94 def parse_test_output(txt):
95 """Parse the output of a test run and return errors, failures.
95 """Parse the output of a test run and return errors, failures.
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 txt : str
99 txt : str
100 Text output of a test run, assumed to contain a line of one of the
100 Text output of a test run, assumed to contain a line of one of the
101 following forms::
101 following forms::
102
102
103 'FAILED (errors=1)'
103 'FAILED (errors=1)'
104 'FAILED (failures=1)'
104 'FAILED (failures=1)'
105 'FAILED (errors=1, failures=1)'
105 'FAILED (errors=1, failures=1)'
106
106
107 Returns
107 Returns
108 -------
108 -------
109 nerr, nfail
109 nerr, nfail
110 number of errors and failures.
110 number of errors and failures.
111 """
111 """
112
112
113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
114 if err_m:
114 if err_m:
115 nerr = int(err_m.group(1))
115 nerr = int(err_m.group(1))
116 nfail = 0
116 nfail = 0
117 return nerr, nfail
117 return nerr, nfail
118
118
119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
120 if fail_m:
120 if fail_m:
121 nerr = 0
121 nerr = 0
122 nfail = int(fail_m.group(1))
122 nfail = int(fail_m.group(1))
123 return nerr, nfail
123 return nerr, nfail
124
124
125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
126 re.MULTILINE)
126 re.MULTILINE)
127 if both_m:
127 if both_m:
128 nerr = int(both_m.group(1))
128 nerr = int(both_m.group(1))
129 nfail = int(both_m.group(2))
129 nfail = int(both_m.group(2))
130 return nerr, nfail
130 return nerr, nfail
131
131
132 # If the input didn't match any of these forms, assume no error/failures
132 # If the input didn't match any of these forms, assume no error/failures
133 return 0, 0
133 return 0, 0
134
134
135
135
136 # So nose doesn't think this is a test
136 # So nose doesn't think this is a test
137 parse_test_output.__test__ = False
137 parse_test_output.__test__ = False
138
138
139
139
140 def default_argv():
140 def default_argv():
141 """Return a valid default argv for creating testing instances of ipython"""
141 """Return a valid default argv for creating testing instances of ipython"""
142
142
143 return ['--quick', # so no config file is loaded
143 return ['--quick', # so no config file is loaded
144 # Other defaults to minimize side effects on stdout
144 # Other defaults to minimize side effects on stdout
145 '--colors=NoColor', '--no-term-title','--no-banner',
145 '--colors=NoColor', '--no-term-title','--no-banner',
146 '--autocall=0']
146 '--autocall=0']
147
147
148
148
149 def default_config():
149 def default_config():
150 """Return a config object with good defaults for testing."""
150 """Return a config object with good defaults for testing."""
151 config = Config()
151 config = Config()
152 config.TerminalInteractiveShell.colors = 'NoColor'
152 config.TerminalInteractiveShell.colors = 'NoColor'
153 config.TerminalTerminalInteractiveShell.term_title = False,
153 config.TerminalTerminalInteractiveShell.term_title = False,
154 config.TerminalInteractiveShell.autocall = 0
154 config.TerminalInteractiveShell.autocall = 0
155 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
155 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
156 config.HistoryManager.hist_file = f.name
156 config.HistoryManager.hist_file = f.name
157 f.close()
157 f.close()
158 config.HistoryManager.db_cache_size = 10000
158 config.HistoryManager.db_cache_size = 10000
159 return config
159 return config
160
160
161
161
162 def get_ipython_cmd(as_string=False):
162 def get_ipython_cmd(as_string=False):
163 """
163 """
164 Return appropriate IPython command line name. By default, this will return
164 Return appropriate IPython command line name. By default, this will return
165 a list that can be used with subprocess.Popen, for example, but passing
165 a list that can be used with subprocess.Popen, for example, but passing
166 `as_string=True` allows for returning the IPython command as a string.
166 `as_string=True` allows for returning the IPython command as a string.
167
167
168 Parameters
168 Parameters
169 ----------
169 ----------
170 as_string: bool
170 as_string: bool
171 Flag to allow to return the command as a string.
171 Flag to allow to return the command as a string.
172 """
172 """
173 ipython_cmd = [sys.executable, "-m", "IPython"]
173 ipython_cmd = [sys.executable, "-m", "IPython"]
174
174
175 if as_string:
175 if as_string:
176 ipython_cmd = " ".join(ipython_cmd)
176 ipython_cmd = " ".join(ipython_cmd)
177
177
178 return ipython_cmd
178 return ipython_cmd
179
179
180 def ipexec(fname, options=None, commands=()):
180 def ipexec(fname, options=None, commands=()):
181 """Utility to call 'ipython filename'.
181 """Utility to call 'ipython filename'.
182
182
183 Starts IPython with a minimal and safe configuration to make startup as fast
183 Starts IPython with a minimal and safe configuration to make startup as fast
184 as possible.
184 as possible.
185
185
186 Note that this starts IPython in a subprocess!
186 Note that this starts IPython in a subprocess!
187
187
188 Parameters
188 Parameters
189 ----------
189 ----------
190 fname : str
190 fname : str
191 Name of file to be executed (should have .py or .ipy extension).
191 Name of file to be executed (should have .py or .ipy extension).
192
192
193 options : optional, list
193 options : optional, list
194 Extra command-line flags to be passed to IPython.
194 Extra command-line flags to be passed to IPython.
195
195
196 commands : optional, list
196 commands : optional, list
197 Commands to send in on stdin
197 Commands to send in on stdin
198
198
199 Returns
199 Returns
200 -------
200 -------
201 (stdout, stderr) of ipython subprocess.
201 (stdout, stderr) of ipython subprocess.
202 """
202 """
203 if options is None: options = []
203 if options is None: options = []
204
204
205 # For these subprocess calls, eliminate all prompt printing so we only see
205 # For these subprocess calls, eliminate all prompt printing so we only see
206 # output from script execution
206 # output from script execution
207 prompt_opts = [ '--PromptManager.in_template=""',
207 prompt_opts = [ '--PromptManager.in_template=""',
208 '--PromptManager.in2_template=""',
208 '--PromptManager.in2_template=""',
209 '--PromptManager.out_template=""'
209 '--PromptManager.out_template=""'
210 ]
210 ]
211 cmdargs = default_argv() + prompt_opts + options
211 cmdargs = default_argv() + prompt_opts + options
212
212
213 test_dir = os.path.dirname(__file__)
213 test_dir = os.path.dirname(__file__)
214
214
215 ipython_cmd = get_ipython_cmd()
215 ipython_cmd = get_ipython_cmd()
216 # Absolute path for filename
216 # Absolute path for filename
217 full_fname = os.path.join(test_dir, fname)
217 full_fname = os.path.join(test_dir, fname)
218 full_cmd = ipython_cmd + cmdargs + [full_fname]
218 full_cmd = ipython_cmd + cmdargs + [full_fname]
219 env = os.environ.copy()
219 env = os.environ.copy()
220 # FIXME: ignore all warnings in ipexec while we have shims
220 # FIXME: ignore all warnings in ipexec while we have shims
221 # should we keep suppressing warnings here, even after removing shims?
221 # should we keep suppressing warnings here, even after removing shims?
222 env['PYTHONWARNINGS'] = 'ignore'
222 env['PYTHONWARNINGS'] = 'ignore'
223 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
223 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
224 for k, v in env.items():
224 for k, v in env.items():
225 # Debug a bizarre failure we've seen on Windows:
225 # Debug a bizarre failure we've seen on Windows:
226 # TypeError: environment can only contain strings
226 # TypeError: environment can only contain strings
227 if not isinstance(v, str):
227 if not isinstance(v, str):
228 print(k, v)
228 print(k, v)
229 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
229 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
230 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
230 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
231 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
231 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
232 # `import readline` causes 'ESC[?1034h' to be output sometimes,
232 # `import readline` causes 'ESC[?1034h' to be output sometimes,
233 # so strip that out before doing comparisons
233 # so strip that out before doing comparisons
234 if out:
234 if out:
235 out = re.sub(r'\x1b\[[^h]+h', '', out)
235 out = re.sub(r'\x1b\[[^h]+h', '', out)
236 return out, err
236 return out, err
237
237
238
238
239 def ipexec_validate(fname, expected_out, expected_err='',
239 def ipexec_validate(fname, expected_out, expected_err='',
240 options=None, commands=()):
240 options=None, commands=()):
241 """Utility to call 'ipython filename' and validate output/error.
241 """Utility to call 'ipython filename' and validate output/error.
242
242
243 This function raises an AssertionError if the validation fails.
243 This function raises an AssertionError if the validation fails.
244
244
245 Note that this starts IPython in a subprocess!
245 Note that this starts IPython in a subprocess!
246
246
247 Parameters
247 Parameters
248 ----------
248 ----------
249 fname : str
249 fname : str
250 Name of the file to be executed (should have .py or .ipy extension).
250 Name of the file to be executed (should have .py or .ipy extension).
251
251
252 expected_out : str
252 expected_out : str
253 Expected stdout of the process.
253 Expected stdout of the process.
254
254
255 expected_err : optional, str
255 expected_err : optional, str
256 Expected stderr of the process.
256 Expected stderr of the process.
257
257
258 options : optional, list
258 options : optional, list
259 Extra command-line flags to be passed to IPython.
259 Extra command-line flags to be passed to IPython.
260
260
261 Returns
261 Returns
262 -------
262 -------
263 None
263 None
264 """
264 """
265
265
266 import nose.tools as nt
266 import nose.tools as nt
267
267
268 out, err = ipexec(fname, options, commands)
268 out, err = ipexec(fname, options, commands)
269 #print 'OUT', out # dbg
269 #print 'OUT', out # dbg
270 #print 'ERR', err # dbg
270 #print 'ERR', err # dbg
271 # If there are any errors, we must check those befor stdout, as they may be
271 # If there are any errors, we must check those befor stdout, as they may be
272 # more informative than simply having an empty stdout.
272 # more informative than simply having an empty stdout.
273 if err:
273 if err:
274 if expected_err:
274 if expected_err:
275 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
275 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
276 else:
276 else:
277 raise ValueError('Running file %r produced error: %r' %
277 raise ValueError('Running file %r produced error: %r' %
278 (fname, err))
278 (fname, err))
279 # If no errors or output on stderr was expected, match stdout
279 # If no errors or output on stderr was expected, match stdout
280 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
280 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
281
281
282
282
283 class TempFileMixin(object):
283 class TempFileMixin(object):
284 """Utility class to create temporary Python/IPython files.
284 """Utility class to create temporary Python/IPython files.
285
285
286 Meant as a mixin class for test cases."""
286 Meant as a mixin class for test cases."""
287
287
288 def mktmp(self, src, ext='.py'):
288 def mktmp(self, src, ext='.py'):
289 """Make a valid python temp file."""
289 """Make a valid python temp file."""
290 fname, f = temp_pyfile(src, ext)
290 fname, f = temp_pyfile(src, ext)
291 self.tmpfile = f
291 self.tmpfile = f
292 self.fname = fname
292 self.fname = fname
293
293
294 def tearDown(self):
294 def tearDown(self):
295 if hasattr(self, 'tmpfile'):
295 if hasattr(self, 'tmpfile'):
296 # If the tmpfile wasn't made because of skipped tests, like in
296 # If the tmpfile wasn't made because of skipped tests, like in
297 # win32, there's nothing to cleanup.
297 # win32, there's nothing to cleanup.
298 self.tmpfile.close()
298 self.tmpfile.close()
299 try:
299 try:
300 os.unlink(self.fname)
300 os.unlink(self.fname)
301 except:
301 except:
302 # On Windows, even though we close the file, we still can't
302 # On Windows, even though we close the file, we still can't
303 # delete it. I have no clue why
303 # delete it. I have no clue why
304 pass
304 pass
305
305
306 pair_fail_msg = ("Testing {0}\n\n"
306 pair_fail_msg = ("Testing {0}\n\n"
307 "In:\n"
307 "In:\n"
308 " {1!r}\n"
308 " {1!r}\n"
309 "Expected:\n"
309 "Expected:\n"
310 " {2!r}\n"
310 " {2!r}\n"
311 "Got:\n"
311 "Got:\n"
312 " {3!r}\n")
312 " {3!r}\n")
313 def check_pairs(func, pairs):
313 def check_pairs(func, pairs):
314 """Utility function for the common case of checking a function with a
314 """Utility function for the common case of checking a function with a
315 sequence of input/output pairs.
315 sequence of input/output pairs.
316
316
317 Parameters
317 Parameters
318 ----------
318 ----------
319 func : callable
319 func : callable
320 The function to be tested. Should accept a single argument.
320 The function to be tested. Should accept a single argument.
321 pairs : iterable
321 pairs : iterable
322 A list of (input, expected_output) tuples.
322 A list of (input, expected_output) tuples.
323
323
324 Returns
324 Returns
325 -------
325 -------
326 None. Raises an AssertionError if any output does not match the expected
326 None. Raises an AssertionError if any output does not match the expected
327 value.
327 value.
328 """
328 """
329 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
329 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
330 for inp, expected in pairs:
330 for inp, expected in pairs:
331 out = func(inp)
331 out = func(inp)
332 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
332 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
333
333
334
334
335 if py3compat.PY3:
335 if py3compat.PY3:
336 MyStringIO = StringIO
336 MyStringIO = StringIO
337 else:
337 else:
338 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
338 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
339 # so we need a class that can handle both.
339 # so we need a class that can handle both.
340 class MyStringIO(StringIO):
340 class MyStringIO(StringIO):
341 def write(self, s):
341 def write(self, s):
342 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
342 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
343 super(MyStringIO, self).write(s)
343 super(MyStringIO, self).write(s)
344
344
345 _re_type = type(re.compile(r''))
345 _re_type = type(re.compile(r''))
346
346
347 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
347 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
348 -------
348 -------
349 {2!s}
349 {2!s}
350 -------
350 -------
351 """
351 """
352
352
353 class AssertPrints(object):
353 class AssertPrints(object):
354 """Context manager for testing that code prints certain text.
354 """Context manager for testing that code prints certain text.
355
355
356 Examples
356 Examples
357 --------
357 --------
358 >>> with AssertPrints("abc", suppress=False):
358 >>> with AssertPrints("abc", suppress=False):
359 ... print("abcd")
359 ... print("abcd")
360 ... print("def")
360 ... print("def")
361 ...
361 ...
362 abcd
362 abcd
363 def
363 def
364 """
364 """
365 def __init__(self, s, channel='stdout', suppress=True):
365 def __init__(self, s, channel='stdout', suppress=True):
366 self.s = s
366 self.s = s
367 if isinstance(self.s, (py3compat.string_types, _re_type)):
367 if isinstance(self.s, (py3compat.string_types, _re_type)):
368 self.s = [self.s]
368 self.s = [self.s]
369 self.channel = channel
369 self.channel = channel
370 self.suppress = suppress
370 self.suppress = suppress
371
371
372 def __enter__(self):
372 def __enter__(self):
373 self.orig_stream = getattr(sys, self.channel)
373 self.orig_stream = getattr(sys, self.channel)
374 self.buffer = MyStringIO()
374 self.buffer = MyStringIO()
375 self.tee = Tee(self.buffer, channel=self.channel)
375 self.tee = Tee(self.buffer, channel=self.channel)
376 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
376 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
377
377
378 def __exit__(self, etype, value, traceback):
378 def __exit__(self, etype, value, traceback):
379 try:
379 try:
380 if value is not None:
380 if value is not None:
381 # If an error was raised, don't check anything else
381 # If an error was raised, don't check anything else
382 return False
382 return False
383 self.tee.flush()
383 self.tee.flush()
384 setattr(sys, self.channel, self.orig_stream)
384 setattr(sys, self.channel, self.orig_stream)
385 printed = self.buffer.getvalue()
385 printed = self.buffer.getvalue()
386 for s in self.s:
386 for s in self.s:
387 if isinstance(s, _re_type):
387 if isinstance(s, _re_type):
388 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
388 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
389 else:
389 else:
390 assert s in printed, notprinted_msg.format(s, self.channel, printed)
390 assert s in printed, notprinted_msg.format(s, self.channel, printed)
391 return False
391 return False
392 finally:
392 finally:
393 self.tee.close()
393 self.tee.close()
394
394
395 printed_msg = """Found {0!r} in printed output (on {1}):
395 printed_msg = """Found {0!r} in printed output (on {1}):
396 -------
396 -------
397 {2!s}
397 {2!s}
398 -------
398 -------
399 """
399 """
400
400
401 class AssertNotPrints(AssertPrints):
401 class AssertNotPrints(AssertPrints):
402 """Context manager for checking that certain output *isn't* produced.
402 """Context manager for checking that certain output *isn't* produced.
403
403
404 Counterpart of AssertPrints"""
404 Counterpart of AssertPrints"""
405 def __exit__(self, etype, value, traceback):
405 def __exit__(self, etype, value, traceback):
406 try:
406 try:
407 if value is not None:
407 if value is not None:
408 # If an error was raised, don't check anything else
408 # If an error was raised, don't check anything else
409 self.tee.close()
409 self.tee.close()
410 return False
410 return False
411 self.tee.flush()
411 self.tee.flush()
412 setattr(sys, self.channel, self.orig_stream)
412 setattr(sys, self.channel, self.orig_stream)
413 printed = self.buffer.getvalue()
413 printed = self.buffer.getvalue()
414 for s in self.s:
414 for s in self.s:
415 if isinstance(s, _re_type):
415 if isinstance(s, _re_type):
416 assert not s.search(printed),printed_msg.format(
416 assert not s.search(printed),printed_msg.format(
417 s.pattern, self.channel, printed)
417 s.pattern, self.channel, printed)
418 else:
418 else:
419 assert s not in printed, printed_msg.format(
419 assert s not in printed, printed_msg.format(
420 s, self.channel, printed)
420 s, self.channel, printed)
421 return False
421 return False
422 finally:
422 finally:
423 self.tee.close()
423 self.tee.close()
424
424
425 @contextmanager
425 @contextmanager
426 def mute_warn():
426 def mute_warn():
427 from IPython.utils import warn
427 from IPython.utils import warn
428 save_warn = warn.warn
428 save_warn = warn.warn
429 warn.warn = lambda *a, **kw: None
429 warn.warn = lambda *a, **kw: None
430 try:
430 try:
431 yield
431 yield
432 finally:
432 finally:
433 warn.warn = save_warn
433 warn.warn = save_warn
434
434
435 @contextmanager
435 @contextmanager
436 def make_tempfile(name):
436 def make_tempfile(name):
437 """ Create an empty, named, temporary file for the duration of the context.
437 """ Create an empty, named, temporary file for the duration of the context.
438 """
438 """
439 f = open(name, 'w')
439 f = open(name, 'w')
440 f.close()
440 f.close()
441 try:
441 try:
442 yield
442 yield
443 finally:
443 finally:
444 os.unlink(name)
444 os.unlink(name)
445
445
446
446
447 @contextmanager
448 def monkeypatch(obj, name, attr):
449 """
450 Context manager to replace attribute named `name` in `obj` with `attr`.
451 """
452 orig = getattr(obj, name)
453 setattr(obj, name, attr)
454 yield
455 setattr(obj, name, orig)
456
457
458 def help_output_test(subcommand=''):
447 def help_output_test(subcommand=''):
459 """test that `ipython [subcommand] -h` works"""
448 """test that `ipython [subcommand] -h` works"""
460 cmd = get_ipython_cmd() + [subcommand, '-h']
449 cmd = get_ipython_cmd() + [subcommand, '-h']
461 out, err, rc = get_output_error_code(cmd)
450 out, err, rc = get_output_error_code(cmd)
462 nt.assert_equal(rc, 0, err)
451 nt.assert_equal(rc, 0, err)
463 nt.assert_not_in("Traceback", err)
452 nt.assert_not_in("Traceback", err)
464 nt.assert_in("Options", out)
453 nt.assert_in("Options", out)
465 nt.assert_in("--help-all", out)
454 nt.assert_in("--help-all", out)
466 return out, err
455 return out, err
467
456
468
457
469 def help_all_output_test(subcommand=''):
458 def help_all_output_test(subcommand=''):
470 """test that `ipython [subcommand] --help-all` works"""
459 """test that `ipython [subcommand] --help-all` works"""
471 cmd = get_ipython_cmd() + [subcommand, '--help-all']
460 cmd = get_ipython_cmd() + [subcommand, '--help-all']
472 out, err, rc = get_output_error_code(cmd)
461 out, err, rc = get_output_error_code(cmd)
473 nt.assert_equal(rc, 0, err)
462 nt.assert_equal(rc, 0, err)
474 nt.assert_not_in("Traceback", err)
463 nt.assert_not_in("Traceback", err)
475 nt.assert_in("Options", out)
464 nt.assert_in("Options", out)
476 nt.assert_in("Class parameters", out)
465 nt.assert_in("Class parameters", out)
477 return out, err
466 return out, err
478
467
479 def assert_big_text_equal(a, b, chunk_size=80):
468 def assert_big_text_equal(a, b, chunk_size=80):
480 """assert that large strings are equal
469 """assert that large strings are equal
481
470
482 Zooms in on first chunk that differs,
471 Zooms in on first chunk that differs,
483 to give better info than vanilla assertEqual for large text blobs.
472 to give better info than vanilla assertEqual for large text blobs.
484 """
473 """
485 for i in range(0, len(a), chunk_size):
474 for i in range(0, len(a), chunk_size):
486 chunk_a = a[i:i + chunk_size]
475 chunk_a = a[i:i + chunk_size]
487 chunk_b = b[i:i + chunk_size]
476 chunk_b = b[i:i + chunk_size]
488 nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % (
477 nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % (
489 i, chunk_a, chunk_b))
478 i, chunk_a, chunk_b))
490
479
491 if len(a) > len(b):
480 if len(a) > len(b):
492 nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % (
481 nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % (
493 len(a), len(b), a[len(b):]
482 len(a), len(b), a[len(b):]
494 ))
483 ))
495 elif len(a) < len(b):
484 elif len(a) < len(b):
496 nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % (
485 nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % (
497 len(a), len(b), b[len(a):]
486 len(a), len(b), b[len(a):]
498 ))
487 ))
@@ -1,94 +1,95
1 #-----------------------------------------------------------------------------
1 # Copyright (c) IPython Development Team.
2 # Copyright (C) 2012 The IPython Development Team
2 # Distributed under the terms of the Modified BSD License.
3 #
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING, distributed as part of this software.
6 #-----------------------------------------------------------------------------
7
3
8 import os
4 import os
9 import sys
5 import sys
10 import unittest
6 import unittest
11 import base64
7 import base64
12
8
9 try:
10 from unittest.mock import patch
11 except ImportError:
12 from mock import patch
13
13 from IPython.kernel import KernelClient
14 from IPython.kernel import KernelClient
14 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
15 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
15 from IPython.utils.tempdir import TemporaryDirectory
16 from IPython.utils.tempdir import TemporaryDirectory
16 from IPython.testing.tools import monkeypatch
17 from IPython.testing.tools import monkeypatch
17 from IPython.testing.decorators import skip_without
18 from IPython.testing.decorators import skip_without
18 from IPython.utils.ipstruct import Struct
19 from IPython.utils.ipstruct import Struct
19
20
20
21
21 SCRIPT_PATH = os.path.join(
22 SCRIPT_PATH = os.path.join(
22 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
23 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
23
24
24
25
25 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
26
27
27 def setUp(self):
28 def setUp(self):
28 client = KernelClient()
29 client = KernelClient()
29 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
30 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
30 self.raw = b'dummy data'
31 self.raw = b'dummy data'
31 self.mime = 'image/png'
32 self.mime = 'image/png'
32 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
33
34
34 def test_no_call_by_default(self):
35 def test_no_call_by_default(self):
35 def raise_if_called(*args, **kwds):
36 def raise_if_called(*args, **kwds):
36 assert False
37 assert False
37
38
38 shell = self.shell
39 shell = self.shell
39 shell.handle_image_PIL = raise_if_called
40 shell.handle_image_PIL = raise_if_called
40 shell.handle_image_stream = raise_if_called
41 shell.handle_image_stream = raise_if_called
41 shell.handle_image_tempfile = raise_if_called
42 shell.handle_image_tempfile = raise_if_called
42 shell.handle_image_callable = raise_if_called
43 shell.handle_image_callable = raise_if_called
43
44
44 shell.handle_image(None, None) # arguments are dummy
45 shell.handle_image(None, None) # arguments are dummy
45
46
46 @skip_without('PIL')
47 @skip_without('PIL')
47 def test_handle_image_PIL(self):
48 def test_handle_image_PIL(self):
48 import PIL.Image
49 import PIL.Image
49
50
50 open_called_with = []
51 open_called_with = []
51 show_called_with = []
52 show_called_with = []
52
53
53 def fake_open(arg):
54 def fake_open(arg):
54 open_called_with.append(arg)
55 open_called_with.append(arg)
55 return Struct(show=lambda: show_called_with.append(None))
56 return Struct(show=lambda: show_called_with.append(None))
56
57
57 with monkeypatch(PIL.Image, 'open', fake_open):
58 with patch.object(PIL.Image, 'open', fake_open):
58 self.shell.handle_image_PIL(self.data, self.mime)
59 self.shell.handle_image_PIL(self.data, self.mime)
59
60
60 self.assertEqual(len(open_called_with), 1)
61 self.assertEqual(len(open_called_with), 1)
61 self.assertEqual(len(show_called_with), 1)
62 self.assertEqual(len(show_called_with), 1)
62 self.assertEqual(open_called_with[0].getvalue(), self.raw)
63 self.assertEqual(open_called_with[0].getvalue(), self.raw)
63
64
64 def check_handler_with_file(self, inpath, handler):
65 def check_handler_with_file(self, inpath, handler):
65 shell = self.shell
66 shell = self.shell
66 configname = '{0}_image_handler'.format(handler)
67 configname = '{0}_image_handler'.format(handler)
67 funcname = 'handle_image_{0}'.format(handler)
68 funcname = 'handle_image_{0}'.format(handler)
68
69
69 assert hasattr(shell, configname)
70 assert hasattr(shell, configname)
70 assert hasattr(shell, funcname)
71 assert hasattr(shell, funcname)
71
72
72 with TemporaryDirectory() as tmpdir:
73 with TemporaryDirectory() as tmpdir:
73 outpath = os.path.join(tmpdir, 'data')
74 outpath = os.path.join(tmpdir, 'data')
74 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
75 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
75 setattr(shell, configname, cmd)
76 setattr(shell, configname, cmd)
76 getattr(shell, funcname)(self.data, self.mime)
77 getattr(shell, funcname)(self.data, self.mime)
77 # cmd is called and file is closed. So it's safe to open now.
78 # cmd is called and file is closed. So it's safe to open now.
78 with open(outpath, 'rb') as file:
79 with open(outpath, 'rb') as file:
79 transferred = file.read()
80 transferred = file.read()
80
81
81 self.assertEqual(transferred, self.raw)
82 self.assertEqual(transferred, self.raw)
82
83
83 def test_handle_image_stream(self):
84 def test_handle_image_stream(self):
84 self.check_handler_with_file('-', 'stream')
85 self.check_handler_with_file('-', 'stream')
85
86
86 def test_handle_image_tempfile(self):
87 def test_handle_image_tempfile(self):
87 self.check_handler_with_file('{file}', 'tempfile')
88 self.check_handler_with_file('{file}', 'tempfile')
88
89
89 def test_handle_image_callable(self):
90 def test_handle_image_callable(self):
90 called_with = []
91 called_with = []
91 self.shell.callable_image_handler = called_with.append
92 self.shell.callable_image_handler = called_with.append
92 self.shell.handle_image_callable(self.data, self.mime)
93 self.shell.handle_image_callable(self.data, self.mime)
93 self.assertEqual(len(called_with), 1)
94 self.assertEqual(len(called_with), 1)
94 assert called_with[0] is self.data
95 assert called_with[0] is self.data
General Comments 0
You need to be logged in to leave comments. Login now