##// END OF EJS Templates
Add failing test for running %run -d twice
Thomas Kluyver -
Show More
@@ -1,507 +1,514 b''
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
14
15
15
16 import functools
16 import functools
17 import os
17 import os
18 from os.path import join as pjoin
18 from os.path import join as pjoin
19 import random
19 import random
20 import sys
20 import sys
21 import textwrap
21 import textwrap
22 import unittest
22 import unittest
23
23
24 try:
24 try:
25 from unittest.mock import patch
25 from unittest.mock import patch
26 except ImportError:
26 except ImportError:
27 from mock import patch
27 from mock import patch
28
28
29 import nose.tools as nt
29 import nose.tools as nt
30 from nose import SkipTest
30 from nose import SkipTest
31
31
32 from IPython.testing import decorators as dec
32 from IPython.testing import decorators as dec
33 from IPython.testing import tools as tt
33 from IPython.testing import tools as tt
34 from IPython.utils import py3compat
34 from IPython.utils import py3compat
35 from IPython.utils.io import capture_output
35 from IPython.utils.io import capture_output
36 from IPython.utils.tempdir import TemporaryDirectory
36 from IPython.utils.tempdir import TemporaryDirectory
37 from IPython.core import debugger
37 from IPython.core import debugger
38
38
39
39
40 def doctest_refbug():
40 def doctest_refbug():
41 """Very nasty problem with references held by multiple runs of a script.
41 """Very nasty problem with references held by multiple runs of a script.
42 See: https://github.com/ipython/ipython/issues/141
42 See: https://github.com/ipython/ipython/issues/141
43
43
44 In [1]: _ip.clear_main_mod_cache()
44 In [1]: _ip.clear_main_mod_cache()
45 # random
45 # random
46
46
47 In [2]: %run refbug
47 In [2]: %run refbug
48
48
49 In [3]: call_f()
49 In [3]: call_f()
50 lowercased: hello
50 lowercased: hello
51
51
52 In [4]: %run refbug
52 In [4]: %run refbug
53
53
54 In [5]: call_f()
54 In [5]: call_f()
55 lowercased: hello
55 lowercased: hello
56 lowercased: hello
56 lowercased: hello
57 """
57 """
58
58
59
59
60 def doctest_run_builtins():
60 def doctest_run_builtins():
61 r"""Check that %run doesn't damage __builtins__.
61 r"""Check that %run doesn't damage __builtins__.
62
62
63 In [1]: import tempfile
63 In [1]: import tempfile
64
64
65 In [2]: bid1 = id(__builtins__)
65 In [2]: bid1 = id(__builtins__)
66
66
67 In [3]: fname = tempfile.mkstemp('.py')[1]
67 In [3]: fname = tempfile.mkstemp('.py')[1]
68
68
69 In [3]: f = open(fname,'w')
69 In [3]: f = open(fname,'w')
70
70
71 In [4]: dummy= f.write('pass\n')
71 In [4]: dummy= f.write('pass\n')
72
72
73 In [5]: f.flush()
73 In [5]: f.flush()
74
74
75 In [6]: t1 = type(__builtins__)
75 In [6]: t1 = type(__builtins__)
76
76
77 In [7]: %run $fname
77 In [7]: %run $fname
78
78
79 In [7]: f.close()
79 In [7]: f.close()
80
80
81 In [8]: bid2 = id(__builtins__)
81 In [8]: bid2 = id(__builtins__)
82
82
83 In [9]: t2 = type(__builtins__)
83 In [9]: t2 = type(__builtins__)
84
84
85 In [10]: t1 == t2
85 In [10]: t1 == t2
86 Out[10]: True
86 Out[10]: True
87
87
88 In [10]: bid1 == bid2
88 In [10]: bid1 == bid2
89 Out[10]: True
89 Out[10]: True
90
90
91 In [12]: try:
91 In [12]: try:
92 ....: os.unlink(fname)
92 ....: os.unlink(fname)
93 ....: except:
93 ....: except:
94 ....: pass
94 ....: pass
95 ....:
95 ....:
96 """
96 """
97
97
98
98
99 def doctest_run_option_parser():
99 def doctest_run_option_parser():
100 r"""Test option parser in %run.
100 r"""Test option parser in %run.
101
101
102 In [1]: %run print_argv.py
102 In [1]: %run print_argv.py
103 []
103 []
104
104
105 In [2]: %run print_argv.py print*.py
105 In [2]: %run print_argv.py print*.py
106 ['print_argv.py']
106 ['print_argv.py']
107
107
108 In [3]: %run -G print_argv.py print*.py
108 In [3]: %run -G print_argv.py print*.py
109 ['print*.py']
109 ['print*.py']
110
110
111 """
111 """
112
112
113
113
114 @dec.skip_win32
114 @dec.skip_win32
115 def doctest_run_option_parser_for_posix():
115 def doctest_run_option_parser_for_posix():
116 r"""Test option parser in %run (Linux/OSX specific).
116 r"""Test option parser in %run (Linux/OSX specific).
117
117
118 You need double quote to escape glob in POSIX systems:
118 You need double quote to escape glob in POSIX systems:
119
119
120 In [1]: %run print_argv.py print\\*.py
120 In [1]: %run print_argv.py print\\*.py
121 ['print*.py']
121 ['print*.py']
122
122
123 You can't use quote to escape glob in POSIX systems:
123 You can't use quote to escape glob in POSIX systems:
124
124
125 In [2]: %run print_argv.py 'print*.py'
125 In [2]: %run print_argv.py 'print*.py'
126 ['print_argv.py']
126 ['print_argv.py']
127
127
128 """
128 """
129
129
130
130
131 @dec.skip_if_not_win32
131 @dec.skip_if_not_win32
132 def doctest_run_option_parser_for_windows():
132 def doctest_run_option_parser_for_windows():
133 r"""Test option parser in %run (Windows specific).
133 r"""Test option parser in %run (Windows specific).
134
134
135 In Windows, you can't escape ``*` `by backslash:
135 In Windows, you can't escape ``*` `by backslash:
136
136
137 In [1]: %run print_argv.py print\\*.py
137 In [1]: %run print_argv.py print\\*.py
138 ['print\\*.py']
138 ['print\\*.py']
139
139
140 You can use quote to escape glob:
140 You can use quote to escape glob:
141
141
142 In [2]: %run print_argv.py 'print*.py'
142 In [2]: %run print_argv.py 'print*.py'
143 ['print*.py']
143 ['print*.py']
144
144
145 """
145 """
146
146
147
147
148 @py3compat.doctest_refactor_print
148 @py3compat.doctest_refactor_print
149 def doctest_reset_del():
149 def doctest_reset_del():
150 """Test that resetting doesn't cause errors in __del__ methods.
150 """Test that resetting doesn't cause errors in __del__ methods.
151
151
152 In [2]: class A(object):
152 In [2]: class A(object):
153 ...: def __del__(self):
153 ...: def __del__(self):
154 ...: print str("Hi")
154 ...: print str("Hi")
155 ...:
155 ...:
156
156
157 In [3]: a = A()
157 In [3]: a = A()
158
158
159 In [4]: get_ipython().reset()
159 In [4]: get_ipython().reset()
160 Hi
160 Hi
161
161
162 In [5]: 1+1
162 In [5]: 1+1
163 Out[5]: 2
163 Out[5]: 2
164 """
164 """
165
165
166 # For some tests, it will be handy to organize them in a class with a common
166 # For some tests, it will be handy to organize them in a class with a common
167 # setup that makes a temp file
167 # setup that makes a temp file
168
168
169 class TestMagicRunPass(tt.TempFileMixin):
169 class TestMagicRunPass(tt.TempFileMixin):
170
170
171 def setup(self):
171 def setup(self):
172 """Make a valid python temp file."""
172 """Make a valid python temp file."""
173 self.mktmp('pass\n')
173 self.mktmp('pass\n')
174
174
175 def run_tmpfile(self):
175 def run_tmpfile(self):
176 _ip = get_ipython()
176 _ip = get_ipython()
177 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
177 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
178 # See below and ticket https://bugs.launchpad.net/bugs/366353
178 # See below and ticket https://bugs.launchpad.net/bugs/366353
179 _ip.magic('run %s' % self.fname)
179 _ip.magic('run %s' % self.fname)
180
180
181 def run_tmpfile_p(self):
181 def run_tmpfile_p(self):
182 _ip = get_ipython()
182 _ip = get_ipython()
183 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
183 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
184 # See below and ticket https://bugs.launchpad.net/bugs/366353
184 # See below and ticket https://bugs.launchpad.net/bugs/366353
185 _ip.magic('run -p %s' % self.fname)
185 _ip.magic('run -p %s' % self.fname)
186
186
187 def test_builtins_id(self):
187 def test_builtins_id(self):
188 """Check that %run doesn't damage __builtins__ """
188 """Check that %run doesn't damage __builtins__ """
189 _ip = get_ipython()
189 _ip = get_ipython()
190 # Test that the id of __builtins__ is not modified by %run
190 # Test that the id of __builtins__ is not modified by %run
191 bid1 = id(_ip.user_ns['__builtins__'])
191 bid1 = id(_ip.user_ns['__builtins__'])
192 self.run_tmpfile()
192 self.run_tmpfile()
193 bid2 = id(_ip.user_ns['__builtins__'])
193 bid2 = id(_ip.user_ns['__builtins__'])
194 nt.assert_equal(bid1, bid2)
194 nt.assert_equal(bid1, bid2)
195
195
196 def test_builtins_type(self):
196 def test_builtins_type(self):
197 """Check that the type of __builtins__ doesn't change with %run.
197 """Check that the type of __builtins__ doesn't change with %run.
198
198
199 However, the above could pass if __builtins__ was already modified to
199 However, the above could pass if __builtins__ was already modified to
200 be a dict (it should be a module) by a previous use of %run. So we
200 be a dict (it should be a module) by a previous use of %run. So we
201 also check explicitly that it really is a module:
201 also check explicitly that it really is a module:
202 """
202 """
203 _ip = get_ipython()
203 _ip = get_ipython()
204 self.run_tmpfile()
204 self.run_tmpfile()
205 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
205 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
206
206
207 def test_run_profile( self ):
207 def test_run_profile( self ):
208 """Test that the option -p, which invokes the profiler, do not
208 """Test that the option -p, which invokes the profiler, do not
209 crash by invoking execfile"""
209 crash by invoking execfile"""
210 get_ipython()
211 self.run_tmpfile_p()
210 self.run_tmpfile_p()
212
211
212 def test_run_debug_twice(self):
213 # https://github.com/ipython/ipython/issues/10028
214 _ip = get_ipython()
215 with tt.fake_input(['c']):
216 _ip.magic('run -d %s' % self.fname)
217 with tt.fake_input(['c']):
218 _ip.magic('run -d %s' % self.fname)
219
213
220
214 class TestMagicRunSimple(tt.TempFileMixin):
221 class TestMagicRunSimple(tt.TempFileMixin):
215
222
216 def test_simpledef(self):
223 def test_simpledef(self):
217 """Test that simple class definitions work."""
224 """Test that simple class definitions work."""
218 src = ("class foo: pass\n"
225 src = ("class foo: pass\n"
219 "def f(): return foo()")
226 "def f(): return foo()")
220 self.mktmp(src)
227 self.mktmp(src)
221 _ip.magic('run %s' % self.fname)
228 _ip.magic('run %s' % self.fname)
222 _ip.run_cell('t = isinstance(f(), foo)')
229 _ip.run_cell('t = isinstance(f(), foo)')
223 nt.assert_true(_ip.user_ns['t'])
230 nt.assert_true(_ip.user_ns['t'])
224
231
225 def test_obj_del(self):
232 def test_obj_del(self):
226 """Test that object's __del__ methods are called on exit."""
233 """Test that object's __del__ methods are called on exit."""
227 if sys.platform == 'win32':
234 if sys.platform == 'win32':
228 try:
235 try:
229 import win32api
236 import win32api
230 except ImportError:
237 except ImportError:
231 raise SkipTest("Test requires pywin32")
238 raise SkipTest("Test requires pywin32")
232 src = ("class A(object):\n"
239 src = ("class A(object):\n"
233 " def __del__(self):\n"
240 " def __del__(self):\n"
234 " print 'object A deleted'\n"
241 " print 'object A deleted'\n"
235 "a = A()\n")
242 "a = A()\n")
236 self.mktmp(py3compat.doctest_refactor_print(src))
243 self.mktmp(py3compat.doctest_refactor_print(src))
237 if dec.module_not_available('sqlite3'):
244 if dec.module_not_available('sqlite3'):
238 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
245 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
239 else:
246 else:
240 err = None
247 err = None
241 tt.ipexec_validate(self.fname, 'object A deleted', err)
248 tt.ipexec_validate(self.fname, 'object A deleted', err)
242
249
243 def test_aggressive_namespace_cleanup(self):
250 def test_aggressive_namespace_cleanup(self):
244 """Test that namespace cleanup is not too aggressive GH-238
251 """Test that namespace cleanup is not too aggressive GH-238
245
252
246 Returning from another run magic deletes the namespace"""
253 Returning from another run magic deletes the namespace"""
247 # see ticket https://github.com/ipython/ipython/issues/238
254 # see ticket https://github.com/ipython/ipython/issues/238
248
255
249 with tt.TempFileMixin() as empty:
256 with tt.TempFileMixin() as empty:
250 empty.mktmp('')
257 empty.mktmp('')
251 # On Windows, the filename will have \users in it, so we need to use the
258 # On Windows, the filename will have \users in it, so we need to use the
252 # repr so that the \u becomes \\u.
259 # repr so that the \u becomes \\u.
253 src = ("ip = get_ipython()\n"
260 src = ("ip = get_ipython()\n"
254 "for i in range(5):\n"
261 "for i in range(5):\n"
255 " try:\n"
262 " try:\n"
256 " ip.magic(%r)\n"
263 " ip.magic(%r)\n"
257 " except NameError as e:\n"
264 " except NameError as e:\n"
258 " print(i)\n"
265 " print(i)\n"
259 " break\n" % ('run ' + empty.fname))
266 " break\n" % ('run ' + empty.fname))
260 self.mktmp(src)
267 self.mktmp(src)
261 _ip.magic('run %s' % self.fname)
268 _ip.magic('run %s' % self.fname)
262 _ip.run_cell('ip == get_ipython()')
269 _ip.run_cell('ip == get_ipython()')
263 nt.assert_equal(_ip.user_ns['i'], 4)
270 nt.assert_equal(_ip.user_ns['i'], 4)
264
271
265 def test_run_second(self):
272 def test_run_second(self):
266 """Test that running a second file doesn't clobber the first, gh-3547
273 """Test that running a second file doesn't clobber the first, gh-3547
267 """
274 """
268 self.mktmp("avar = 1\n"
275 self.mktmp("avar = 1\n"
269 "def afunc():\n"
276 "def afunc():\n"
270 " return avar\n")
277 " return avar\n")
271
278
272 with tt.TempFileMixin() as empty:
279 with tt.TempFileMixin() as empty:
273 empty.mktmp("")
280 empty.mktmp("")
274
281
275 _ip.magic('run %s' % self.fname)
282 _ip.magic('run %s' % self.fname)
276 _ip.magic('run %s' % empty.fname)
283 _ip.magic('run %s' % empty.fname)
277 nt.assert_equal(_ip.user_ns['afunc'](), 1)
284 nt.assert_equal(_ip.user_ns['afunc'](), 1)
278
285
279 @dec.skip_win32
286 @dec.skip_win32
280 def test_tclass(self):
287 def test_tclass(self):
281 mydir = os.path.dirname(__file__)
288 mydir = os.path.dirname(__file__)
282 tc = os.path.join(mydir, 'tclass')
289 tc = os.path.join(mydir, 'tclass')
283 src = ("%%run '%s' C-first\n"
290 src = ("%%run '%s' C-first\n"
284 "%%run '%s' C-second\n"
291 "%%run '%s' C-second\n"
285 "%%run '%s' C-third\n") % (tc, tc, tc)
292 "%%run '%s' C-third\n") % (tc, tc, tc)
286 self.mktmp(src, '.ipy')
293 self.mktmp(src, '.ipy')
287 out = """\
294 out = """\
288 ARGV 1-: ['C-first']
295 ARGV 1-: ['C-first']
289 ARGV 1-: ['C-second']
296 ARGV 1-: ['C-second']
290 tclass.py: deleting object: C-first
297 tclass.py: deleting object: C-first
291 ARGV 1-: ['C-third']
298 ARGV 1-: ['C-third']
292 tclass.py: deleting object: C-second
299 tclass.py: deleting object: C-second
293 tclass.py: deleting object: C-third
300 tclass.py: deleting object: C-third
294 """
301 """
295 if dec.module_not_available('sqlite3'):
302 if dec.module_not_available('sqlite3'):
296 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
303 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
297 else:
304 else:
298 err = None
305 err = None
299 tt.ipexec_validate(self.fname, out, err)
306 tt.ipexec_validate(self.fname, out, err)
300
307
301 def test_run_i_after_reset(self):
308 def test_run_i_after_reset(self):
302 """Check that %run -i still works after %reset (gh-693)"""
309 """Check that %run -i still works after %reset (gh-693)"""
303 src = "yy = zz\n"
310 src = "yy = zz\n"
304 self.mktmp(src)
311 self.mktmp(src)
305 _ip.run_cell("zz = 23")
312 _ip.run_cell("zz = 23")
306 _ip.magic('run -i %s' % self.fname)
313 _ip.magic('run -i %s' % self.fname)
307 nt.assert_equal(_ip.user_ns['yy'], 23)
314 nt.assert_equal(_ip.user_ns['yy'], 23)
308 _ip.magic('reset -f')
315 _ip.magic('reset -f')
309 _ip.run_cell("zz = 23")
316 _ip.run_cell("zz = 23")
310 _ip.magic('run -i %s' % self.fname)
317 _ip.magic('run -i %s' % self.fname)
311 nt.assert_equal(_ip.user_ns['yy'], 23)
318 nt.assert_equal(_ip.user_ns['yy'], 23)
312
319
313 def test_unicode(self):
320 def test_unicode(self):
314 """Check that files in odd encodings are accepted."""
321 """Check that files in odd encodings are accepted."""
315 mydir = os.path.dirname(__file__)
322 mydir = os.path.dirname(__file__)
316 na = os.path.join(mydir, 'nonascii.py')
323 na = os.path.join(mydir, 'nonascii.py')
317 _ip.magic('run "%s"' % na)
324 _ip.magic('run "%s"' % na)
318 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
325 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
319
326
320 def test_run_py_file_attribute(self):
327 def test_run_py_file_attribute(self):
321 """Test handling of `__file__` attribute in `%run <file>.py`."""
328 """Test handling of `__file__` attribute in `%run <file>.py`."""
322 src = "t = __file__\n"
329 src = "t = __file__\n"
323 self.mktmp(src)
330 self.mktmp(src)
324 _missing = object()
331 _missing = object()
325 file1 = _ip.user_ns.get('__file__', _missing)
332 file1 = _ip.user_ns.get('__file__', _missing)
326 _ip.magic('run %s' % self.fname)
333 _ip.magic('run %s' % self.fname)
327 file2 = _ip.user_ns.get('__file__', _missing)
334 file2 = _ip.user_ns.get('__file__', _missing)
328
335
329 # Check that __file__ was equal to the filename in the script's
336 # Check that __file__ was equal to the filename in the script's
330 # namespace.
337 # namespace.
331 nt.assert_equal(_ip.user_ns['t'], self.fname)
338 nt.assert_equal(_ip.user_ns['t'], self.fname)
332
339
333 # Check that __file__ was not leaked back into user_ns.
340 # Check that __file__ was not leaked back into user_ns.
334 nt.assert_equal(file1, file2)
341 nt.assert_equal(file1, file2)
335
342
336 def test_run_ipy_file_attribute(self):
343 def test_run_ipy_file_attribute(self):
337 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
344 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
338 src = "t = __file__\n"
345 src = "t = __file__\n"
339 self.mktmp(src, ext='.ipy')
346 self.mktmp(src, ext='.ipy')
340 _missing = object()
347 _missing = object()
341 file1 = _ip.user_ns.get('__file__', _missing)
348 file1 = _ip.user_ns.get('__file__', _missing)
342 _ip.magic('run %s' % self.fname)
349 _ip.magic('run %s' % self.fname)
343 file2 = _ip.user_ns.get('__file__', _missing)
350 file2 = _ip.user_ns.get('__file__', _missing)
344
351
345 # Check that __file__ was equal to the filename in the script's
352 # Check that __file__ was equal to the filename in the script's
346 # namespace.
353 # namespace.
347 nt.assert_equal(_ip.user_ns['t'], self.fname)
354 nt.assert_equal(_ip.user_ns['t'], self.fname)
348
355
349 # Check that __file__ was not leaked back into user_ns.
356 # Check that __file__ was not leaked back into user_ns.
350 nt.assert_equal(file1, file2)
357 nt.assert_equal(file1, file2)
351
358
352 def test_run_formatting(self):
359 def test_run_formatting(self):
353 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
360 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
354 src = "pass"
361 src = "pass"
355 self.mktmp(src)
362 self.mktmp(src)
356 _ip.magic('run -t -N 1 %s' % self.fname)
363 _ip.magic('run -t -N 1 %s' % self.fname)
357 _ip.magic('run -t -N 10 %s' % self.fname)
364 _ip.magic('run -t -N 10 %s' % self.fname)
358
365
359 def test_ignore_sys_exit(self):
366 def test_ignore_sys_exit(self):
360 """Test the -e option to ignore sys.exit()"""
367 """Test the -e option to ignore sys.exit()"""
361 src = "import sys; sys.exit(1)"
368 src = "import sys; sys.exit(1)"
362 self.mktmp(src)
369 self.mktmp(src)
363 with tt.AssertPrints('SystemExit'):
370 with tt.AssertPrints('SystemExit'):
364 _ip.magic('run %s' % self.fname)
371 _ip.magic('run %s' % self.fname)
365
372
366 with tt.AssertNotPrints('SystemExit'):
373 with tt.AssertNotPrints('SystemExit'):
367 _ip.magic('run -e %s' % self.fname)
374 _ip.magic('run -e %s' % self.fname)
368
375
369 def test_run_nb(self):
376 def test_run_nb(self):
370 """Test %run notebook.ipynb"""
377 """Test %run notebook.ipynb"""
371 from nbformat import v4, writes
378 from nbformat import v4, writes
372 nb = v4.new_notebook(
379 nb = v4.new_notebook(
373 cells=[
380 cells=[
374 v4.new_markdown_cell("The Ultimate Question of Everything"),
381 v4.new_markdown_cell("The Ultimate Question of Everything"),
375 v4.new_code_cell("answer=42")
382 v4.new_code_cell("answer=42")
376 ]
383 ]
377 )
384 )
378 src = writes(nb, version=4)
385 src = writes(nb, version=4)
379 self.mktmp(src, ext='.ipynb')
386 self.mktmp(src, ext='.ipynb')
380
387
381 _ip.magic("run %s" % self.fname)
388 _ip.magic("run %s" % self.fname)
382
389
383 nt.assert_equal(_ip.user_ns['answer'], 42)
390 nt.assert_equal(_ip.user_ns['answer'], 42)
384
391
385
392
386
393
387 class TestMagicRunWithPackage(unittest.TestCase):
394 class TestMagicRunWithPackage(unittest.TestCase):
388
395
389 def writefile(self, name, content):
396 def writefile(self, name, content):
390 path = os.path.join(self.tempdir.name, name)
397 path = os.path.join(self.tempdir.name, name)
391 d = os.path.dirname(path)
398 d = os.path.dirname(path)
392 if not os.path.isdir(d):
399 if not os.path.isdir(d):
393 os.makedirs(d)
400 os.makedirs(d)
394 with open(path, 'w') as f:
401 with open(path, 'w') as f:
395 f.write(textwrap.dedent(content))
402 f.write(textwrap.dedent(content))
396
403
397 def setUp(self):
404 def setUp(self):
398 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
405 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
399 """Temporary valid python package name."""
406 """Temporary valid python package name."""
400
407
401 self.value = int(random.random() * 10000)
408 self.value = int(random.random() * 10000)
402
409
403 self.tempdir = TemporaryDirectory()
410 self.tempdir = TemporaryDirectory()
404 self.__orig_cwd = py3compat.getcwd()
411 self.__orig_cwd = py3compat.getcwd()
405 sys.path.insert(0, self.tempdir.name)
412 sys.path.insert(0, self.tempdir.name)
406
413
407 self.writefile(os.path.join(package, '__init__.py'), '')
414 self.writefile(os.path.join(package, '__init__.py'), '')
408 self.writefile(os.path.join(package, 'sub.py'), """
415 self.writefile(os.path.join(package, 'sub.py'), """
409 x = {0!r}
416 x = {0!r}
410 """.format(self.value))
417 """.format(self.value))
411 self.writefile(os.path.join(package, 'relative.py'), """
418 self.writefile(os.path.join(package, 'relative.py'), """
412 from .sub import x
419 from .sub import x
413 """)
420 """)
414 self.writefile(os.path.join(package, 'absolute.py'), """
421 self.writefile(os.path.join(package, 'absolute.py'), """
415 from {0}.sub import x
422 from {0}.sub import x
416 """.format(package))
423 """.format(package))
417
424
418 def tearDown(self):
425 def tearDown(self):
419 os.chdir(self.__orig_cwd)
426 os.chdir(self.__orig_cwd)
420 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
427 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
421 self.tempdir.cleanup()
428 self.tempdir.cleanup()
422
429
423 def check_run_submodule(self, submodule, opts=''):
430 def check_run_submodule(self, submodule, opts=''):
424 _ip.user_ns.pop('x', None)
431 _ip.user_ns.pop('x', None)
425 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
432 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
426 self.assertEqual(_ip.user_ns['x'], self.value,
433 self.assertEqual(_ip.user_ns['x'], self.value,
427 'Variable `x` is not loaded from module `{0}`.'
434 'Variable `x` is not loaded from module `{0}`.'
428 .format(submodule))
435 .format(submodule))
429
436
430 def test_run_submodule_with_absolute_import(self):
437 def test_run_submodule_with_absolute_import(self):
431 self.check_run_submodule('absolute')
438 self.check_run_submodule('absolute')
432
439
433 def test_run_submodule_with_relative_import(self):
440 def test_run_submodule_with_relative_import(self):
434 """Run submodule that has a relative import statement (#2727)."""
441 """Run submodule that has a relative import statement (#2727)."""
435 self.check_run_submodule('relative')
442 self.check_run_submodule('relative')
436
443
437 def test_prun_submodule_with_absolute_import(self):
444 def test_prun_submodule_with_absolute_import(self):
438 self.check_run_submodule('absolute', '-p')
445 self.check_run_submodule('absolute', '-p')
439
446
440 def test_prun_submodule_with_relative_import(self):
447 def test_prun_submodule_with_relative_import(self):
441 self.check_run_submodule('relative', '-p')
448 self.check_run_submodule('relative', '-p')
442
449
443 def with_fake_debugger(func):
450 def with_fake_debugger(func):
444 @functools.wraps(func)
451 @functools.wraps(func)
445 def wrapper(*args, **kwds):
452 def wrapper(*args, **kwds):
446 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
453 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
447 return func(*args, **kwds)
454 return func(*args, **kwds)
448 return wrapper
455 return wrapper
449
456
450 @with_fake_debugger
457 @with_fake_debugger
451 def test_debug_run_submodule_with_absolute_import(self):
458 def test_debug_run_submodule_with_absolute_import(self):
452 self.check_run_submodule('absolute', '-d')
459 self.check_run_submodule('absolute', '-d')
453
460
454 @with_fake_debugger
461 @with_fake_debugger
455 def test_debug_run_submodule_with_relative_import(self):
462 def test_debug_run_submodule_with_relative_import(self):
456 self.check_run_submodule('relative', '-d')
463 self.check_run_submodule('relative', '-d')
457
464
458 def test_run__name__():
465 def test_run__name__():
459 with TemporaryDirectory() as td:
466 with TemporaryDirectory() as td:
460 path = pjoin(td, 'foo.py')
467 path = pjoin(td, 'foo.py')
461 with open(path, 'w') as f:
468 with open(path, 'w') as f:
462 f.write("q = __name__")
469 f.write("q = __name__")
463
470
464 _ip.user_ns.pop('q', None)
471 _ip.user_ns.pop('q', None)
465 _ip.magic('run {}'.format(path))
472 _ip.magic('run {}'.format(path))
466 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
473 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
467
474
468 _ip.magic('run -n {}'.format(path))
475 _ip.magic('run -n {}'.format(path))
469 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
476 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
470
477
471 def test_run_tb():
478 def test_run_tb():
472 """Test traceback offset in %run"""
479 """Test traceback offset in %run"""
473 with TemporaryDirectory() as td:
480 with TemporaryDirectory() as td:
474 path = pjoin(td, 'foo.py')
481 path = pjoin(td, 'foo.py')
475 with open(path, 'w') as f:
482 with open(path, 'w') as f:
476 f.write('\n'.join([
483 f.write('\n'.join([
477 "def foo():",
484 "def foo():",
478 " return bar()",
485 " return bar()",
479 "def bar():",
486 "def bar():",
480 " raise RuntimeError('hello!')",
487 " raise RuntimeError('hello!')",
481 "foo()",
488 "foo()",
482 ]))
489 ]))
483 with capture_output() as io:
490 with capture_output() as io:
484 _ip.magic('run {}'.format(path))
491 _ip.magic('run {}'.format(path))
485 out = io.stdout
492 out = io.stdout
486 nt.assert_not_in("execfile", out)
493 nt.assert_not_in("execfile", out)
487 nt.assert_in("RuntimeError", out)
494 nt.assert_in("RuntimeError", out)
488 nt.assert_equal(out.count("---->"), 3)
495 nt.assert_equal(out.count("---->"), 3)
489
496
490 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
497 @dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
491 def test_script_tb():
498 def test_script_tb():
492 """Test traceback offset in `ipython script.py`"""
499 """Test traceback offset in `ipython script.py`"""
493 with TemporaryDirectory() as td:
500 with TemporaryDirectory() as td:
494 path = pjoin(td, 'foo.py')
501 path = pjoin(td, 'foo.py')
495 with open(path, 'w') as f:
502 with open(path, 'w') as f:
496 f.write('\n'.join([
503 f.write('\n'.join([
497 "def foo():",
504 "def foo():",
498 " return bar()",
505 " return bar()",
499 "def bar():",
506 "def bar():",
500 " raise RuntimeError('hello!')",
507 " raise RuntimeError('hello!')",
501 "foo()",
508 "foo()",
502 ]))
509 ]))
503 out, err = tt.ipexec(path)
510 out, err = tt.ipexec(path)
504 nt.assert_not_in("execfile", out)
511 nt.assert_not_in("execfile", out)
505 nt.assert_in("RuntimeError", out)
512 nt.assert_in("RuntimeError", out)
506 nt.assert_equal(out.count("---->"), 3)
513 nt.assert_equal(out.count("---->"), 3)
507
514
@@ -1,467 +1,476 b''
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
8
9 #-----------------------------------------------------------------------------
9 # Copyright (c) IPython Development Team.
10 # Copyright (C) 2009 The IPython Development Team
10 # Distributed under the terms of the Modified BSD License.
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
11
20 import os
12 import os
21 import re
13 import re
22 import sys
14 import sys
23 import tempfile
15 import tempfile
24
16
25 from contextlib import contextmanager
17 from contextlib import contextmanager
26 from io import StringIO
18 from io import StringIO
27 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from unittest.mock import patch
28
21
29 try:
22 try:
30 # These tools are used by parts of the runtime, so we make the nose
23 # These tools are used by parts of the runtime, so we make the nose
31 # dependency optional at this point. Nose is a hard dependency to run the
24 # dependency optional at this point. Nose is a hard dependency to run the
32 # test suite, but NOT to use ipython itself.
25 # test suite, but NOT to use ipython itself.
33 import nose.tools as nt
26 import nose.tools as nt
34 has_nose = True
27 has_nose = True
35 except ImportError:
28 except ImportError:
36 has_nose = False
29 has_nose = False
37
30
38 from traitlets.config.loader import Config
31 from traitlets.config.loader import Config
39 from IPython.utils.process import get_output_error_code
32 from IPython.utils.process import get_output_error_code
40 from IPython.utils.text import list_strings
33 from IPython.utils.text import list_strings
41 from IPython.utils.io import temp_pyfile, Tee
34 from IPython.utils.io import temp_pyfile, Tee
42 from IPython.utils import py3compat
35 from IPython.utils import py3compat
43 from IPython.utils.encoding import DEFAULT_ENCODING
36 from IPython.utils.encoding import DEFAULT_ENCODING
44
37
45 from . import decorators as dec
38 from . import decorators as dec
46 from . import skipdoctest
39 from . import skipdoctest
47
40
48 #-----------------------------------------------------------------------------
49 # Functions and classes
50 #-----------------------------------------------------------------------------
51
41
52 # The docstring for full_path doctests differently on win32 (different path
42 # The docstring for full_path doctests differently on win32 (different path
53 # separator) so just skip the doctest there. The example remains informative.
43 # separator) so just skip the doctest there. The example remains informative.
54 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
44 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
55
45
56 @doctest_deco
46 @doctest_deco
57 def full_path(startPath,files):
47 def full_path(startPath,files):
58 """Make full paths for all the listed files, based on startPath.
48 """Make full paths for all the listed files, based on startPath.
59
49
60 Only the base part of startPath is kept, since this routine is typically
50 Only the base part of startPath is kept, since this routine is typically
61 used with a script's ``__file__`` variable as startPath. The base of startPath
51 used with a script's ``__file__`` variable as startPath. The base of startPath
62 is then prepended to all the listed files, forming the output list.
52 is then prepended to all the listed files, forming the output list.
63
53
64 Parameters
54 Parameters
65 ----------
55 ----------
66 startPath : string
56 startPath : string
67 Initial path to use as the base for the results. This path is split
57 Initial path to use as the base for the results. This path is split
68 using os.path.split() and only its first component is kept.
58 using os.path.split() and only its first component is kept.
69
59
70 files : string or list
60 files : string or list
71 One or more files.
61 One or more files.
72
62
73 Examples
63 Examples
74 --------
64 --------
75
65
76 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
66 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 ['/foo/a.txt', '/foo/b.txt']
67 ['/foo/a.txt', '/foo/b.txt']
78
68
79 >>> full_path('/foo',['a.txt','b.txt'])
69 >>> full_path('/foo',['a.txt','b.txt'])
80 ['/a.txt', '/b.txt']
70 ['/a.txt', '/b.txt']
81
71
82 If a single file is given, the output is still a list::
72 If a single file is given, the output is still a list::
83
73
84 >>> full_path('/foo','a.txt')
74 >>> full_path('/foo','a.txt')
85 ['/a.txt']
75 ['/a.txt']
86 """
76 """
87
77
88 files = list_strings(files)
78 files = list_strings(files)
89 base = os.path.split(startPath)[0]
79 base = os.path.split(startPath)[0]
90 return [ os.path.join(base,f) for f in files ]
80 return [ os.path.join(base,f) for f in files ]
91
81
92
82
93 def parse_test_output(txt):
83 def parse_test_output(txt):
94 """Parse the output of a test run and return errors, failures.
84 """Parse the output of a test run and return errors, failures.
95
85
96 Parameters
86 Parameters
97 ----------
87 ----------
98 txt : str
88 txt : str
99 Text output of a test run, assumed to contain a line of one of the
89 Text output of a test run, assumed to contain a line of one of the
100 following forms::
90 following forms::
101
91
102 'FAILED (errors=1)'
92 'FAILED (errors=1)'
103 'FAILED (failures=1)'
93 'FAILED (failures=1)'
104 'FAILED (errors=1, failures=1)'
94 'FAILED (errors=1, failures=1)'
105
95
106 Returns
96 Returns
107 -------
97 -------
108 nerr, nfail
98 nerr, nfail
109 number of errors and failures.
99 number of errors and failures.
110 """
100 """
111
101
112 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
102 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 if err_m:
103 if err_m:
114 nerr = int(err_m.group(1))
104 nerr = int(err_m.group(1))
115 nfail = 0
105 nfail = 0
116 return nerr, nfail
106 return nerr, nfail
117
107
118 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
108 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 if fail_m:
109 if fail_m:
120 nerr = 0
110 nerr = 0
121 nfail = int(fail_m.group(1))
111 nfail = int(fail_m.group(1))
122 return nerr, nfail
112 return nerr, nfail
123
113
124 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
114 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 re.MULTILINE)
115 re.MULTILINE)
126 if both_m:
116 if both_m:
127 nerr = int(both_m.group(1))
117 nerr = int(both_m.group(1))
128 nfail = int(both_m.group(2))
118 nfail = int(both_m.group(2))
129 return nerr, nfail
119 return nerr, nfail
130
120
131 # If the input didn't match any of these forms, assume no error/failures
121 # If the input didn't match any of these forms, assume no error/failures
132 return 0, 0
122 return 0, 0
133
123
134
124
135 # So nose doesn't think this is a test
125 # So nose doesn't think this is a test
136 parse_test_output.__test__ = False
126 parse_test_output.__test__ = False
137
127
138
128
139 def default_argv():
129 def default_argv():
140 """Return a valid default argv for creating testing instances of ipython"""
130 """Return a valid default argv for creating testing instances of ipython"""
141
131
142 return ['--quick', # so no config file is loaded
132 return ['--quick', # so no config file is loaded
143 # Other defaults to minimize side effects on stdout
133 # Other defaults to minimize side effects on stdout
144 '--colors=NoColor', '--no-term-title','--no-banner',
134 '--colors=NoColor', '--no-term-title','--no-banner',
145 '--autocall=0']
135 '--autocall=0']
146
136
147
137
148 def default_config():
138 def default_config():
149 """Return a config object with good defaults for testing."""
139 """Return a config object with good defaults for testing."""
150 config = Config()
140 config = Config()
151 config.TerminalInteractiveShell.colors = 'NoColor'
141 config.TerminalInteractiveShell.colors = 'NoColor'
152 config.TerminalTerminalInteractiveShell.term_title = False,
142 config.TerminalTerminalInteractiveShell.term_title = False,
153 config.TerminalInteractiveShell.autocall = 0
143 config.TerminalInteractiveShell.autocall = 0
154 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
144 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
155 config.HistoryManager.hist_file = f.name
145 config.HistoryManager.hist_file = f.name
156 f.close()
146 f.close()
157 config.HistoryManager.db_cache_size = 10000
147 config.HistoryManager.db_cache_size = 10000
158 return config
148 return config
159
149
160
150
161 def get_ipython_cmd(as_string=False):
151 def get_ipython_cmd(as_string=False):
162 """
152 """
163 Return appropriate IPython command line name. By default, this will return
153 Return appropriate IPython command line name. By default, this will return
164 a list that can be used with subprocess.Popen, for example, but passing
154 a list that can be used with subprocess.Popen, for example, but passing
165 `as_string=True` allows for returning the IPython command as a string.
155 `as_string=True` allows for returning the IPython command as a string.
166
156
167 Parameters
157 Parameters
168 ----------
158 ----------
169 as_string: bool
159 as_string: bool
170 Flag to allow to return the command as a string.
160 Flag to allow to return the command as a string.
171 """
161 """
172 ipython_cmd = [sys.executable, "-m", "IPython"]
162 ipython_cmd = [sys.executable, "-m", "IPython"]
173
163
174 if as_string:
164 if as_string:
175 ipython_cmd = " ".join(ipython_cmd)
165 ipython_cmd = " ".join(ipython_cmd)
176
166
177 return ipython_cmd
167 return ipython_cmd
178
168
179 def ipexec(fname, options=None, commands=()):
169 def ipexec(fname, options=None, commands=()):
180 """Utility to call 'ipython filename'.
170 """Utility to call 'ipython filename'.
181
171
182 Starts IPython with a minimal and safe configuration to make startup as fast
172 Starts IPython with a minimal and safe configuration to make startup as fast
183 as possible.
173 as possible.
184
174
185 Note that this starts IPython in a subprocess!
175 Note that this starts IPython in a subprocess!
186
176
187 Parameters
177 Parameters
188 ----------
178 ----------
189 fname : str
179 fname : str
190 Name of file to be executed (should have .py or .ipy extension).
180 Name of file to be executed (should have .py or .ipy extension).
191
181
192 options : optional, list
182 options : optional, list
193 Extra command-line flags to be passed to IPython.
183 Extra command-line flags to be passed to IPython.
194
184
195 commands : optional, list
185 commands : optional, list
196 Commands to send in on stdin
186 Commands to send in on stdin
197
187
198 Returns
188 Returns
199 -------
189 -------
200 (stdout, stderr) of ipython subprocess.
190 (stdout, stderr) of ipython subprocess.
201 """
191 """
202 if options is None: options = []
192 if options is None: options = []
203
193
204 cmdargs = default_argv() + options
194 cmdargs = default_argv() + options
205
195
206 test_dir = os.path.dirname(__file__)
196 test_dir = os.path.dirname(__file__)
207
197
208 ipython_cmd = get_ipython_cmd()
198 ipython_cmd = get_ipython_cmd()
209 # Absolute path for filename
199 # Absolute path for filename
210 full_fname = os.path.join(test_dir, fname)
200 full_fname = os.path.join(test_dir, fname)
211 full_cmd = ipython_cmd + cmdargs + [full_fname]
201 full_cmd = ipython_cmd + cmdargs + [full_fname]
212 env = os.environ.copy()
202 env = os.environ.copy()
213 # FIXME: ignore all warnings in ipexec while we have shims
203 # FIXME: ignore all warnings in ipexec while we have shims
214 # should we keep suppressing warnings here, even after removing shims?
204 # should we keep suppressing warnings here, even after removing shims?
215 env['PYTHONWARNINGS'] = 'ignore'
205 env['PYTHONWARNINGS'] = 'ignore'
216 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
206 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
217 for k, v in env.items():
207 for k, v in env.items():
218 # Debug a bizarre failure we've seen on Windows:
208 # Debug a bizarre failure we've seen on Windows:
219 # TypeError: environment can only contain strings
209 # TypeError: environment can only contain strings
220 if not isinstance(v, str):
210 if not isinstance(v, str):
221 print(k, v)
211 print(k, v)
222 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
212 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
223 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
213 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
224 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
214 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
225 # `import readline` causes 'ESC[?1034h' to be output sometimes,
215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
226 # so strip that out before doing comparisons
216 # so strip that out before doing comparisons
227 if out:
217 if out:
228 out = re.sub(r'\x1b\[[^h]+h', '', out)
218 out = re.sub(r'\x1b\[[^h]+h', '', out)
229 return out, err
219 return out, err
230
220
231
221
232 def ipexec_validate(fname, expected_out, expected_err='',
222 def ipexec_validate(fname, expected_out, expected_err='',
233 options=None, commands=()):
223 options=None, commands=()):
234 """Utility to call 'ipython filename' and validate output/error.
224 """Utility to call 'ipython filename' and validate output/error.
235
225
236 This function raises an AssertionError if the validation fails.
226 This function raises an AssertionError if the validation fails.
237
227
238 Note that this starts IPython in a subprocess!
228 Note that this starts IPython in a subprocess!
239
229
240 Parameters
230 Parameters
241 ----------
231 ----------
242 fname : str
232 fname : str
243 Name of the file to be executed (should have .py or .ipy extension).
233 Name of the file to be executed (should have .py or .ipy extension).
244
234
245 expected_out : str
235 expected_out : str
246 Expected stdout of the process.
236 Expected stdout of the process.
247
237
248 expected_err : optional, str
238 expected_err : optional, str
249 Expected stderr of the process.
239 Expected stderr of the process.
250
240
251 options : optional, list
241 options : optional, list
252 Extra command-line flags to be passed to IPython.
242 Extra command-line flags to be passed to IPython.
253
243
254 Returns
244 Returns
255 -------
245 -------
256 None
246 None
257 """
247 """
258
248
259 import nose.tools as nt
249 import nose.tools as nt
260
250
261 out, err = ipexec(fname, options, commands)
251 out, err = ipexec(fname, options, commands)
262 #print 'OUT', out # dbg
252 #print 'OUT', out # dbg
263 #print 'ERR', err # dbg
253 #print 'ERR', err # dbg
264 # If there are any errors, we must check those befor stdout, as they may be
254 # If there are any errors, we must check those befor stdout, as they may be
265 # more informative than simply having an empty stdout.
255 # more informative than simply having an empty stdout.
266 if err:
256 if err:
267 if expected_err:
257 if expected_err:
268 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
258 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
269 else:
259 else:
270 raise ValueError('Running file %r produced error: %r' %
260 raise ValueError('Running file %r produced error: %r' %
271 (fname, err))
261 (fname, err))
272 # If no errors or output on stderr was expected, match stdout
262 # If no errors or output on stderr was expected, match stdout
273 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
263 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
274
264
275
265
276 class TempFileMixin(object):
266 class TempFileMixin(object):
277 """Utility class to create temporary Python/IPython files.
267 """Utility class to create temporary Python/IPython files.
278
268
279 Meant as a mixin class for test cases."""
269 Meant as a mixin class for test cases."""
280
270
281 def mktmp(self, src, ext='.py'):
271 def mktmp(self, src, ext='.py'):
282 """Make a valid python temp file."""
272 """Make a valid python temp file."""
283 fname, f = temp_pyfile(src, ext)
273 fname, f = temp_pyfile(src, ext)
284 self.tmpfile = f
274 self.tmpfile = f
285 self.fname = fname
275 self.fname = fname
286
276
287 def tearDown(self):
277 def tearDown(self):
288 if hasattr(self, 'tmpfile'):
278 if hasattr(self, 'tmpfile'):
289 # If the tmpfile wasn't made because of skipped tests, like in
279 # If the tmpfile wasn't made because of skipped tests, like in
290 # win32, there's nothing to cleanup.
280 # win32, there's nothing to cleanup.
291 self.tmpfile.close()
281 self.tmpfile.close()
292 try:
282 try:
293 os.unlink(self.fname)
283 os.unlink(self.fname)
294 except:
284 except:
295 # On Windows, even though we close the file, we still can't
285 # On Windows, even though we close the file, we still can't
296 # delete it. I have no clue why
286 # delete it. I have no clue why
297 pass
287 pass
298
288
299 def __enter__(self):
289 def __enter__(self):
300 return self
290 return self
301
291
302 def __exit__(self, exc_type, exc_value, traceback):
292 def __exit__(self, exc_type, exc_value, traceback):
303 self.tearDown()
293 self.tearDown()
304
294
305
295
306 pair_fail_msg = ("Testing {0}\n\n"
296 pair_fail_msg = ("Testing {0}\n\n"
307 "In:\n"
297 "In:\n"
308 " {1!r}\n"
298 " {1!r}\n"
309 "Expected:\n"
299 "Expected:\n"
310 " {2!r}\n"
300 " {2!r}\n"
311 "Got:\n"
301 "Got:\n"
312 " {3!r}\n")
302 " {3!r}\n")
313 def check_pairs(func, pairs):
303 def check_pairs(func, pairs):
314 """Utility function for the common case of checking a function with a
304 """Utility function for the common case of checking a function with a
315 sequence of input/output pairs.
305 sequence of input/output pairs.
316
306
317 Parameters
307 Parameters
318 ----------
308 ----------
319 func : callable
309 func : callable
320 The function to be tested. Should accept a single argument.
310 The function to be tested. Should accept a single argument.
321 pairs : iterable
311 pairs : iterable
322 A list of (input, expected_output) tuples.
312 A list of (input, expected_output) tuples.
323
313
324 Returns
314 Returns
325 -------
315 -------
326 None. Raises an AssertionError if any output does not match the expected
316 None. Raises an AssertionError if any output does not match the expected
327 value.
317 value.
328 """
318 """
329 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
319 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
330 for inp, expected in pairs:
320 for inp, expected in pairs:
331 out = func(inp)
321 out = func(inp)
332 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
322 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
333
323
334
324
335 if py3compat.PY3:
325 if py3compat.PY3:
336 MyStringIO = StringIO
326 MyStringIO = StringIO
337 else:
327 else:
338 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
328 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
339 # so we need a class that can handle both.
329 # so we need a class that can handle both.
340 class MyStringIO(StringIO):
330 class MyStringIO(StringIO):
341 def write(self, s):
331 def write(self, s):
342 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
332 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
343 super(MyStringIO, self).write(s)
333 super(MyStringIO, self).write(s)
344
334
345 _re_type = type(re.compile(r''))
335 _re_type = type(re.compile(r''))
346
336
347 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
337 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
348 -------
338 -------
349 {2!s}
339 {2!s}
350 -------
340 -------
351 """
341 """
352
342
353 class AssertPrints(object):
343 class AssertPrints(object):
354 """Context manager for testing that code prints certain text.
344 """Context manager for testing that code prints certain text.
355
345
356 Examples
346 Examples
357 --------
347 --------
358 >>> with AssertPrints("abc", suppress=False):
348 >>> with AssertPrints("abc", suppress=False):
359 ... print("abcd")
349 ... print("abcd")
360 ... print("def")
350 ... print("def")
361 ...
351 ...
362 abcd
352 abcd
363 def
353 def
364 """
354 """
365 def __init__(self, s, channel='stdout', suppress=True):
355 def __init__(self, s, channel='stdout', suppress=True):
366 self.s = s
356 self.s = s
367 if isinstance(self.s, (py3compat.string_types, _re_type)):
357 if isinstance(self.s, (py3compat.string_types, _re_type)):
368 self.s = [self.s]
358 self.s = [self.s]
369 self.channel = channel
359 self.channel = channel
370 self.suppress = suppress
360 self.suppress = suppress
371
361
372 def __enter__(self):
362 def __enter__(self):
373 self.orig_stream = getattr(sys, self.channel)
363 self.orig_stream = getattr(sys, self.channel)
374 self.buffer = MyStringIO()
364 self.buffer = MyStringIO()
375 self.tee = Tee(self.buffer, channel=self.channel)
365 self.tee = Tee(self.buffer, channel=self.channel)
376 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
366 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
377
367
378 def __exit__(self, etype, value, traceback):
368 def __exit__(self, etype, value, traceback):
379 try:
369 try:
380 if value is not None:
370 if value is not None:
381 # If an error was raised, don't check anything else
371 # If an error was raised, don't check anything else
382 return False
372 return False
383 self.tee.flush()
373 self.tee.flush()
384 setattr(sys, self.channel, self.orig_stream)
374 setattr(sys, self.channel, self.orig_stream)
385 printed = self.buffer.getvalue()
375 printed = self.buffer.getvalue()
386 for s in self.s:
376 for s in self.s:
387 if isinstance(s, _re_type):
377 if isinstance(s, _re_type):
388 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
378 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
389 else:
379 else:
390 assert s in printed, notprinted_msg.format(s, self.channel, printed)
380 assert s in printed, notprinted_msg.format(s, self.channel, printed)
391 return False
381 return False
392 finally:
382 finally:
393 self.tee.close()
383 self.tee.close()
394
384
395 printed_msg = """Found {0!r} in printed output (on {1}):
385 printed_msg = """Found {0!r} in printed output (on {1}):
396 -------
386 -------
397 {2!s}
387 {2!s}
398 -------
388 -------
399 """
389 """
400
390
401 class AssertNotPrints(AssertPrints):
391 class AssertNotPrints(AssertPrints):
402 """Context manager for checking that certain output *isn't* produced.
392 """Context manager for checking that certain output *isn't* produced.
403
393
404 Counterpart of AssertPrints"""
394 Counterpart of AssertPrints"""
405 def __exit__(self, etype, value, traceback):
395 def __exit__(self, etype, value, traceback):
406 try:
396 try:
407 if value is not None:
397 if value is not None:
408 # If an error was raised, don't check anything else
398 # If an error was raised, don't check anything else
409 self.tee.close()
399 self.tee.close()
410 return False
400 return False
411 self.tee.flush()
401 self.tee.flush()
412 setattr(sys, self.channel, self.orig_stream)
402 setattr(sys, self.channel, self.orig_stream)
413 printed = self.buffer.getvalue()
403 printed = self.buffer.getvalue()
414 for s in self.s:
404 for s in self.s:
415 if isinstance(s, _re_type):
405 if isinstance(s, _re_type):
416 assert not s.search(printed),printed_msg.format(
406 assert not s.search(printed),printed_msg.format(
417 s.pattern, self.channel, printed)
407 s.pattern, self.channel, printed)
418 else:
408 else:
419 assert s not in printed, printed_msg.format(
409 assert s not in printed, printed_msg.format(
420 s, self.channel, printed)
410 s, self.channel, printed)
421 return False
411 return False
422 finally:
412 finally:
423 self.tee.close()
413 self.tee.close()
424
414
425 @contextmanager
415 @contextmanager
426 def mute_warn():
416 def mute_warn():
427 from IPython.utils import warn
417 from IPython.utils import warn
428 save_warn = warn.warn
418 save_warn = warn.warn
429 warn.warn = lambda *a, **kw: None
419 warn.warn = lambda *a, **kw: None
430 try:
420 try:
431 yield
421 yield
432 finally:
422 finally:
433 warn.warn = save_warn
423 warn.warn = save_warn
434
424
435 @contextmanager
425 @contextmanager
436 def make_tempfile(name):
426 def make_tempfile(name):
437 """ Create an empty, named, temporary file for the duration of the context.
427 """ Create an empty, named, temporary file for the duration of the context.
438 """
428 """
439 f = open(name, 'w')
429 f = open(name, 'w')
440 f.close()
430 f.close()
441 try:
431 try:
442 yield
432 yield
443 finally:
433 finally:
444 os.unlink(name)
434 os.unlink(name)
445
435
436 def fake_input(inputs):
437 """Temporarily replace the input() function to return the given values
438
439 Use as a context manager:
440
441 with fake_input(['result1', 'result2']):
442 ...
443
444 Values are returned in order. If input() is called again after the last value
445 was used, EOFError is raised.
446 """
447 it = iter(inputs)
448 def mock_input(prompt=''):
449 try:
450 return next(it)
451 except StopIteration:
452 raise EOFError('No more inputs given')
453
454 return patch('builtins.input', mock_input)
446
455
447 def help_output_test(subcommand=''):
456 def help_output_test(subcommand=''):
448 """test that `ipython [subcommand] -h` works"""
457 """test that `ipython [subcommand] -h` works"""
449 cmd = get_ipython_cmd() + [subcommand, '-h']
458 cmd = get_ipython_cmd() + [subcommand, '-h']
450 out, err, rc = get_output_error_code(cmd)
459 out, err, rc = get_output_error_code(cmd)
451 nt.assert_equal(rc, 0, err)
460 nt.assert_equal(rc, 0, err)
452 nt.assert_not_in("Traceback", err)
461 nt.assert_not_in("Traceback", err)
453 nt.assert_in("Options", out)
462 nt.assert_in("Options", out)
454 nt.assert_in("--help-all", out)
463 nt.assert_in("--help-all", out)
455 return out, err
464 return out, err
456
465
457
466
458 def help_all_output_test(subcommand=''):
467 def help_all_output_test(subcommand=''):
459 """test that `ipython [subcommand] --help-all` works"""
468 """test that `ipython [subcommand] --help-all` works"""
460 cmd = get_ipython_cmd() + [subcommand, '--help-all']
469 cmd = get_ipython_cmd() + [subcommand, '--help-all']
461 out, err, rc = get_output_error_code(cmd)
470 out, err, rc = get_output_error_code(cmd)
462 nt.assert_equal(rc, 0, err)
471 nt.assert_equal(rc, 0, err)
463 nt.assert_not_in("Traceback", err)
472 nt.assert_not_in("Traceback", err)
464 nt.assert_in("Options", out)
473 nt.assert_in("Options", out)
465 nt.assert_in("Class parameters", out)
474 nt.assert_in("Class parameters", out)
466 return out, err
475 return out, err
467
476
General Comments 0
You need to be logged in to leave comments. Login now