##// END OF EJS Templates
Mechanism for testing terminal interact loop in-process....
Thomas Kluyver -
Show More
@@ -1,203 +1,295 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Authors
4 Authors
5 -------
5 -------
6 * Julian Taylor
6 * Julian Taylor
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # stdlib
18 # stdlib
19 import sys
19 import sys
20 import types
20 import unittest
21 import unittest
21
22
23 from IPython.core.inputtransformer import InputTransformer
22 from IPython.testing.decorators import skipif
24 from IPython.testing.decorators import skipif
23 from IPython.utils import py3compat
25 from IPython.utils import py3compat
24 from IPython.testing import tools as tt
26 from IPython.testing import tools as tt
25
27
28 # Decorator for interaction loop tests -----------------------------------------
29
30 class mock_input_helper(object):
31 """Machinery for tests of the main interact loop.
32
33 Used by the mock_input decorator.
34 """
35 def __init__(self, testgen):
36 self.testgen = testgen
37 self.exception = None
38 self.ip = get_ipython()
39
40 def __enter__(self):
41 self.orig_raw_input = self.ip.raw_input
42 self.ip.raw_input = self.fake_input
43 return self
44
45 def __exit__(self, etype, value, tb):
46 self.ip.raw_input = self.orig_raw_input
47
48 def fake_input(self, prompt):
49 try:
50 return next(self.testgen)
51 except StopIteration:
52 self.ip.exit_now = True
53 return u''
54 except:
55 self.exception = sys.exc_info()
56 self.ip.exit_now = True
57 return u''
58
59 def mock_input(testfunc):
60 """Decorator for tests of the main interact loop.
61
62 Write the test as a generator, yield-ing the input strings, which IPython
63 will see as if they were typed in at the prompt.
64 """
65 def test_method(self):
66 testgen = testfunc(self)
67 with mock_input_helper(testgen) as mih:
68 mih.ip.interact(display_banner=False)
69
70 if mih.exception is not None:
71 # Re-raise captured exception
72 etype, value, tb = mih.exception
73 import traceback
74 traceback.print_tb(tb, file=sys.stdout)
75 del tb # Avoid reference loop
76 raise value
77
78 return test_method
79
80 # Test classes -----------------------------------------------------------------
81
26 class InteractiveShellTestCase(unittest.TestCase):
82 class InteractiveShellTestCase(unittest.TestCase):
27 def rl_hist_entries(self, rl, n):
83 def rl_hist_entries(self, rl, n):
28 """Get last n readline history entries as a list"""
84 """Get last n readline history entries as a list"""
29 return [rl.get_history_item(rl.get_current_history_length() - x)
85 return [rl.get_history_item(rl.get_current_history_length() - x)
30 for x in range(n - 1, -1, -1)]
86 for x in range(n - 1, -1, -1)]
31
87
32 def test_runs_without_rl(self):
88 def test_runs_without_rl(self):
33 """Test that function does not throw without readline"""
89 """Test that function does not throw without readline"""
34 ip = get_ipython()
90 ip = get_ipython()
35 ip.has_readline = False
91 ip.has_readline = False
36 ip.readline = None
92 ip.readline = None
37 ip._replace_rlhist_multiline(u'source', 0)
93 ip._replace_rlhist_multiline(u'source', 0)
38
94
39 @skipif(not get_ipython().has_readline, 'no readline')
95 @skipif(not get_ipython().has_readline, 'no readline')
40 def test_runs_without_remove_history_item(self):
96 def test_runs_without_remove_history_item(self):
41 """Test that function does not throw on windows without
97 """Test that function does not throw on windows without
42 remove_history_item"""
98 remove_history_item"""
43 ip = get_ipython()
99 ip = get_ipython()
44 if hasattr(ip.readline, 'remove_history_item'):
100 if hasattr(ip.readline, 'remove_history_item'):
45 del ip.readline.remove_history_item
101 del ip.readline.remove_history_item
46 ip._replace_rlhist_multiline(u'source', 0)
102 ip._replace_rlhist_multiline(u'source', 0)
47
103
48 @skipif(not get_ipython().has_readline, 'no readline')
104 @skipif(not get_ipython().has_readline, 'no readline')
49 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
105 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
50 'no remove_history_item')
106 'no remove_history_item')
51 def test_replace_multiline_hist_disabled(self):
107 def test_replace_multiline_hist_disabled(self):
52 """Test that multiline replace does nothing if disabled"""
108 """Test that multiline replace does nothing if disabled"""
53 ip = get_ipython()
109 ip = get_ipython()
54 ip.multiline_history = False
110 ip.multiline_history = False
55
111
56 ghist = [u'line1', u'line2']
112 ghist = [u'line1', u'line2']
57 for h in ghist:
113 for h in ghist:
58 ip.readline.add_history(h)
114 ip.readline.add_history(h)
59 hlen_b4_cell = ip.readline.get_current_history_length()
115 hlen_b4_cell = ip.readline.get_current_history_length()
60 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€\nsource2',
116 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€\nsource2',
61 hlen_b4_cell)
117 hlen_b4_cell)
62
118
63 self.assertEqual(ip.readline.get_current_history_length(),
119 self.assertEqual(ip.readline.get_current_history_length(),
64 hlen_b4_cell)
120 hlen_b4_cell)
65 hist = self.rl_hist_entries(ip.readline, 2)
121 hist = self.rl_hist_entries(ip.readline, 2)
66 self.assertEqual(hist, ghist)
122 self.assertEqual(hist, ghist)
67
123
68 @skipif(not get_ipython().has_readline, 'no readline')
124 @skipif(not get_ipython().has_readline, 'no readline')
69 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
125 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
70 'no remove_history_item')
126 'no remove_history_item')
71 def test_replace_multiline_hist_adds(self):
127 def test_replace_multiline_hist_adds(self):
72 """Test that multiline replace function adds history"""
128 """Test that multiline replace function adds history"""
73 ip = get_ipython()
129 ip = get_ipython()
74
130
75 hlen_b4_cell = ip.readline.get_current_history_length()
131 hlen_b4_cell = ip.readline.get_current_history_length()
76 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€', hlen_b4_cell)
132 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€', hlen_b4_cell)
77
133
78 self.assertEqual(hlen_b4_cell,
134 self.assertEqual(hlen_b4_cell,
79 ip.readline.get_current_history_length())
135 ip.readline.get_current_history_length())
80
136
81 @skipif(not get_ipython().has_readline, 'no readline')
137 @skipif(not get_ipython().has_readline, 'no readline')
82 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
138 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
83 'no remove_history_item')
139 'no remove_history_item')
84 def test_replace_multiline_hist_keeps_history(self):
140 def test_replace_multiline_hist_keeps_history(self):
85 """Test that multiline replace does not delete history"""
141 """Test that multiline replace does not delete history"""
86 ip = get_ipython()
142 ip = get_ipython()
87 ip.multiline_history = True
143 ip.multiline_history = True
88
144
89 ghist = [u'line1', u'line2']
145 ghist = [u'line1', u'line2']
90 for h in ghist:
146 for h in ghist:
91 ip.readline.add_history(h)
147 ip.readline.add_history(h)
92
148
93 #start cell
149 #start cell
94 hlen_b4_cell = ip.readline.get_current_history_length()
150 hlen_b4_cell = ip.readline.get_current_history_length()
95 # nothing added to rl history, should do nothing
151 # nothing added to rl history, should do nothing
96 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€\nsource2',
152 hlen_b4_cell = ip._replace_rlhist_multiline(u'sourc€\nsource2',
97 hlen_b4_cell)
153 hlen_b4_cell)
98
154
99 self.assertEqual(ip.readline.get_current_history_length(),
155 self.assertEqual(ip.readline.get_current_history_length(),
100 hlen_b4_cell)
156 hlen_b4_cell)
101 hist = self.rl_hist_entries(ip.readline, 2)
157 hist = self.rl_hist_entries(ip.readline, 2)
102 self.assertEqual(hist, ghist)
158 self.assertEqual(hist, ghist)
103
159
104
160
105 @skipif(not get_ipython().has_readline, 'no readline')
161 @skipif(not get_ipython().has_readline, 'no readline')
106 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
162 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
107 'no remove_history_item')
163 'no remove_history_item')
108 def test_replace_multiline_hist_replaces_twice(self):
164 def test_replace_multiline_hist_replaces_twice(self):
109 """Test that multiline entries are replaced twice"""
165 """Test that multiline entries are replaced twice"""
110 ip = get_ipython()
166 ip = get_ipython()
111 ip.multiline_history = True
167 ip.multiline_history = True
112
168
113 ip.readline.add_history(u'line0')
169 ip.readline.add_history(u'line0')
114 #start cell
170 #start cell
115 hlen_b4_cell = ip.readline.get_current_history_length()
171 hlen_b4_cell = ip.readline.get_current_history_length()
116 ip.readline.add_history('l€ne1')
172 ip.readline.add_history('l€ne1')
117 ip.readline.add_history('line2')
173 ip.readline.add_history('line2')
118 #replace cell with single line
174 #replace cell with single line
119 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne1\nline2',
175 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne1\nline2',
120 hlen_b4_cell)
176 hlen_b4_cell)
121 ip.readline.add_history('l€ne3')
177 ip.readline.add_history('l€ne3')
122 ip.readline.add_history('line4')
178 ip.readline.add_history('line4')
123 #replace cell with single line
179 #replace cell with single line
124 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne3\nline4',
180 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne3\nline4',
125 hlen_b4_cell)
181 hlen_b4_cell)
126
182
127 self.assertEqual(ip.readline.get_current_history_length(),
183 self.assertEqual(ip.readline.get_current_history_length(),
128 hlen_b4_cell)
184 hlen_b4_cell)
129 hist = self.rl_hist_entries(ip.readline, 3)
185 hist = self.rl_hist_entries(ip.readline, 3)
130 expected = [u'line0', u'l€ne1\nline2', u'l€ne3\nline4']
186 expected = [u'line0', u'l€ne1\nline2', u'l€ne3\nline4']
131 # perform encoding, in case of casting due to ASCII locale
187 # perform encoding, in case of casting due to ASCII locale
132 enc = sys.stdin.encoding or "utf-8"
188 enc = sys.stdin.encoding or "utf-8"
133 expected = [ py3compat.unicode_to_str(e, enc) for e in expected ]
189 expected = [ py3compat.unicode_to_str(e, enc) for e in expected ]
134 self.assertEqual(hist, expected)
190 self.assertEqual(hist, expected)
135
191
136
192
137 @skipif(not get_ipython().has_readline, 'no readline')
193 @skipif(not get_ipython().has_readline, 'no readline')
138 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
194 @skipif(not hasattr(get_ipython().readline, 'remove_history_item'),
139 'no remove_history_item')
195 'no remove_history_item')
140 def test_replace_multiline_hist_replaces_empty_line(self):
196 def test_replace_multiline_hist_replaces_empty_line(self):
141 """Test that multiline history skips empty line cells"""
197 """Test that multiline history skips empty line cells"""
142 ip = get_ipython()
198 ip = get_ipython()
143 ip.multiline_history = True
199 ip.multiline_history = True
144
200
145 ip.readline.add_history(u'line0')
201 ip.readline.add_history(u'line0')
146 #start cell
202 #start cell
147 hlen_b4_cell = ip.readline.get_current_history_length()
203 hlen_b4_cell = ip.readline.get_current_history_length()
148 ip.readline.add_history('l€ne1')
204 ip.readline.add_history('l€ne1')
149 ip.readline.add_history('line2')
205 ip.readline.add_history('line2')
150 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne1\nline2',
206 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne1\nline2',
151 hlen_b4_cell)
207 hlen_b4_cell)
152 ip.readline.add_history('')
208 ip.readline.add_history('')
153 hlen_b4_cell = ip._replace_rlhist_multiline(u'', hlen_b4_cell)
209 hlen_b4_cell = ip._replace_rlhist_multiline(u'', hlen_b4_cell)
154 ip.readline.add_history('l€ne3')
210 ip.readline.add_history('l€ne3')
155 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne3', hlen_b4_cell)
211 hlen_b4_cell = ip._replace_rlhist_multiline(u'l€ne3', hlen_b4_cell)
156 ip.readline.add_history(' ')
212 ip.readline.add_history(' ')
157 hlen_b4_cell = ip._replace_rlhist_multiline(' ', hlen_b4_cell)
213 hlen_b4_cell = ip._replace_rlhist_multiline(' ', hlen_b4_cell)
158 ip.readline.add_history('\t')
214 ip.readline.add_history('\t')
159 ip.readline.add_history('\t ')
215 ip.readline.add_history('\t ')
160 hlen_b4_cell = ip._replace_rlhist_multiline('\t', hlen_b4_cell)
216 hlen_b4_cell = ip._replace_rlhist_multiline('\t', hlen_b4_cell)
161 ip.readline.add_history('line4')
217 ip.readline.add_history('line4')
162 hlen_b4_cell = ip._replace_rlhist_multiline(u'line4', hlen_b4_cell)
218 hlen_b4_cell = ip._replace_rlhist_multiline(u'line4', hlen_b4_cell)
163
219
164 self.assertEqual(ip.readline.get_current_history_length(),
220 self.assertEqual(ip.readline.get_current_history_length(),
165 hlen_b4_cell)
221 hlen_b4_cell)
166 hist = self.rl_hist_entries(ip.readline, 4)
222 hist = self.rl_hist_entries(ip.readline, 4)
167 # expect no empty cells in history
223 # expect no empty cells in history
168 expected = [u'line0', u'l€ne1\nline2', u'l€ne3', u'line4']
224 expected = [u'line0', u'l€ne1\nline2', u'l€ne3', u'line4']
169 # perform encoding, in case of casting due to ASCII locale
225 # perform encoding, in case of casting due to ASCII locale
170 enc = sys.stdin.encoding or "utf-8"
226 enc = sys.stdin.encoding or "utf-8"
171 expected = [ py3compat.unicode_to_str(e, enc) for e in expected ]
227 expected = [ py3compat.unicode_to_str(e, enc) for e in expected ]
172 self.assertEqual(hist, expected)
228 self.assertEqual(hist, expected)
173
229
230 @mock_input
231 def test_inputtransformer_syntaxerror(self):
232 ip = get_ipython()
233 transformer = SyntaxErrorTransformer()
234 ip.input_splitter.python_line_transforms.append(transformer)
235 ip.input_transformer_manager.python_line_transforms.append(transformer)
236
237 try:
238 #raise Exception
239 with tt.AssertPrints('4', suppress=False):
240 yield u'print(2*2)'
241
242 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
243 yield u'print(2345) # syntaxerror'
244
245 with tt.AssertPrints('16', suppress=False):
246 yield u'print(4*4)'
247
248 finally:
249 ip.input_splitter.python_line_transforms.remove(transformer)
250 ip.input_transformer_manager.python_line_transforms.remove(transformer)
251
252
253 class SyntaxErrorTransformer(InputTransformer):
254 def push(self, line):
255 pos = line.find('syntaxerror')
256 if pos >= 0:
257 e = SyntaxError('input contains "syntaxerror"')
258 e.text = line
259 e.offset = pos + 1
260 raise e
261 return line
262
263 def reset(self):
264 pass
265
174 class TerminalMagicsTestCase(unittest.TestCase):
266 class TerminalMagicsTestCase(unittest.TestCase):
175 def test_paste_magics_message(self):
267 def test_paste_magics_message(self):
176 """Test that an IndentationError while using paste magics doesn't
268 """Test that an IndentationError while using paste magics doesn't
177 trigger a message about paste magics and also the opposite."""
269 trigger a message about paste magics and also the opposite."""
178
270
179 ip = get_ipython()
271 ip = get_ipython()
180 s = ('for a in range(5):\n'
272 s = ('for a in range(5):\n'
181 'print(a)')
273 'print(a)')
182
274
183 tm = ip.magics_manager.registry['TerminalMagics']
275 tm = ip.magics_manager.registry['TerminalMagics']
184 with tt.AssertPrints("If you want to paste code into IPython, try the "
276 with tt.AssertPrints("If you want to paste code into IPython, try the "
185 "%paste and %cpaste magic functions."):
277 "%paste and %cpaste magic functions."):
186 ip.run_cell(s)
278 ip.run_cell(s)
187
279
188 with tt.AssertNotPrints("If you want to paste code into IPython, try the "
280 with tt.AssertNotPrints("If you want to paste code into IPython, try the "
189 "%paste and %cpaste magic functions."):
281 "%paste and %cpaste magic functions."):
190 tm.store_or_execute(s, name=None)
282 tm.store_or_execute(s, name=None)
191
283
192 def test_paste_magics_blankline(self):
284 def test_paste_magics_blankline(self):
193 """Test that code with a blank line doesn't get split (gh-3246)."""
285 """Test that code with a blank line doesn't get split (gh-3246)."""
194 ip = get_ipython()
286 ip = get_ipython()
195 s = ('def pasted_func(a):\n'
287 s = ('def pasted_func(a):\n'
196 ' b = a+1\n'
288 ' b = a+1\n'
197 '\n'
289 '\n'
198 ' return b')
290 ' return b')
199
291
200 tm = ip.magics_manager.registry['TerminalMagics']
292 tm = ip.magics_manager.registry['TerminalMagics']
201 tm.store_or_execute(s, name=None)
293 tm.store_or_execute(s, name=None)
202
294
203 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
295 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
@@ -1,451 +1,444 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 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 >>> full_path('/foo','a.txt')
84 >>> full_path('/foo','a.txt')
85 ['/a.txt']
85 ['/a.txt']
86 """
86 """
87
87
88 files = list_strings(files)
88 files = list_strings(files)
89 base = os.path.split(startPath)[0]
89 base = os.path.split(startPath)[0]
90 return [ os.path.join(base,f) for f in files ]
90 return [ os.path.join(base,f) for f in files ]
91
91
92
92
93 def parse_test_output(txt):
93 def parse_test_output(txt):
94 """Parse the output of a test run and return errors, failures.
94 """Parse the output of a test run and return errors, failures.
95
95
96 Parameters
96 Parameters
97 ----------
97 ----------
98 txt : str
98 txt : str
99 Text output of a test run, assumed to contain a line of one of the
99 Text output of a test run, assumed to contain a line of one of the
100 following forms::
100 following forms::
101
101
102 'FAILED (errors=1)'
102 'FAILED (errors=1)'
103 'FAILED (failures=1)'
103 'FAILED (failures=1)'
104 'FAILED (errors=1, failures=1)'
104 'FAILED (errors=1, failures=1)'
105
105
106 Returns
106 Returns
107 -------
107 -------
108 nerr, nfail: number of errors and failures.
108 nerr, nfail: number of errors and failures.
109 """
109 """
110
110
111 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
111 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
112 if err_m:
112 if err_m:
113 nerr = int(err_m.group(1))
113 nerr = int(err_m.group(1))
114 nfail = 0
114 nfail = 0
115 return nerr, nfail
115 return nerr, nfail
116
116
117 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
117 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
118 if fail_m:
118 if fail_m:
119 nerr = 0
119 nerr = 0
120 nfail = int(fail_m.group(1))
120 nfail = int(fail_m.group(1))
121 return nerr, nfail
121 return nerr, nfail
122
122
123 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
123 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
124 re.MULTILINE)
124 re.MULTILINE)
125 if both_m:
125 if both_m:
126 nerr = int(both_m.group(1))
126 nerr = int(both_m.group(1))
127 nfail = int(both_m.group(2))
127 nfail = int(both_m.group(2))
128 return nerr, nfail
128 return nerr, nfail
129
129
130 # If the input didn't match any of these forms, assume no error/failures
130 # If the input didn't match any of these forms, assume no error/failures
131 return 0, 0
131 return 0, 0
132
132
133
133
134 # So nose doesn't think this is a test
134 # So nose doesn't think this is a test
135 parse_test_output.__test__ = False
135 parse_test_output.__test__ = False
136
136
137
137
138 def default_argv():
138 def default_argv():
139 """Return a valid default argv for creating testing instances of ipython"""
139 """Return a valid default argv for creating testing instances of ipython"""
140
140
141 return ['--quick', # so no config file is loaded
141 return ['--quick', # so no config file is loaded
142 # Other defaults to minimize side effects on stdout
142 # Other defaults to minimize side effects on stdout
143 '--colors=NoColor', '--no-term-title','--no-banner',
143 '--colors=NoColor', '--no-term-title','--no-banner',
144 '--autocall=0']
144 '--autocall=0']
145
145
146
146
147 def default_config():
147 def default_config():
148 """Return a config object with good defaults for testing."""
148 """Return a config object with good defaults for testing."""
149 config = Config()
149 config = Config()
150 config.TerminalInteractiveShell.colors = 'NoColor'
150 config.TerminalInteractiveShell.colors = 'NoColor'
151 config.TerminalTerminalInteractiveShell.term_title = False,
151 config.TerminalTerminalInteractiveShell.term_title = False,
152 config.TerminalInteractiveShell.autocall = 0
152 config.TerminalInteractiveShell.autocall = 0
153 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
153 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
154 config.HistoryManager.db_cache_size = 10000
154 config.HistoryManager.db_cache_size = 10000
155 return config
155 return config
156
156
157
157
158 def get_ipython_cmd(as_string=False):
158 def get_ipython_cmd(as_string=False):
159 """
159 """
160 Return appropriate IPython command line name. By default, this will return
160 Return appropriate IPython command line name. By default, this will return
161 a list that can be used with subprocess.Popen, for example, but passing
161 a list that can be used with subprocess.Popen, for example, but passing
162 `as_string=True` allows for returning the IPython command as a string.
162 `as_string=True` allows for returning the IPython command as a string.
163
163
164 Parameters
164 Parameters
165 ----------
165 ----------
166 as_string: bool
166 as_string: bool
167 Flag to allow to return the command as a string.
167 Flag to allow to return the command as a string.
168 """
168 """
169 ipython_cmd = [sys.executable, "-m", "IPython"]
169 ipython_cmd = [sys.executable, "-m", "IPython"]
170
170
171 if as_string:
171 if as_string:
172 ipython_cmd = " ".join(ipython_cmd)
172 ipython_cmd = " ".join(ipython_cmd)
173
173
174 return ipython_cmd
174 return ipython_cmd
175
175
176 def ipexec(fname, options=None, pipe=False):
176 def ipexec(fname, options=None):
177 """Utility to call 'ipython filename'.
177 """Utility to call 'ipython filename'.
178
178
179 Starts IPython with a minimal and safe configuration to make startup as fast
179 Starts IPython with a minimal and safe configuration to make startup as fast
180 as possible.
180 as possible.
181
181
182 Note that this starts IPython in a subprocess!
182 Note that this starts IPython in a subprocess!
183
183
184 Parameters
184 Parameters
185 ----------
185 ----------
186 fname : str
186 fname : str
187 Name of file to be executed (should have .py or .ipy extension).
187 Name of file to be executed (should have .py or .ipy extension).
188
188
189 options : optional, list
189 options : optional, list
190 Extra command-line flags to be passed to IPython.
190 Extra command-line flags to be passed to IPython.
191
191
192 pipe : optional, boolean
193 Pipe fname into IPython as stdin instead of calling it as external file
194
195 Returns
192 Returns
196 -------
193 -------
197 (stdout, stderr) of ipython subprocess.
194 (stdout, stderr) of ipython subprocess.
198 """
195 """
199 if options is None: options = []
196 if options is None: options = []
200
197
201 # For these subprocess calls, eliminate all prompt printing so we only see
198 # For these subprocess calls, eliminate all prompt printing so we only see
202 # output from script execution
199 # output from script execution
203 prompt_opts = [ '--PromptManager.in_template=""',
200 prompt_opts = [ '--PromptManager.in_template=""',
204 '--PromptManager.in2_template=""',
201 '--PromptManager.in2_template=""',
205 '--PromptManager.out_template=""'
202 '--PromptManager.out_template=""'
206 ]
203 ]
207 cmdargs = default_argv() + prompt_opts + options
204 cmdargs = default_argv() + prompt_opts + options
208
205
209 test_dir = os.path.dirname(__file__)
206 test_dir = os.path.dirname(__file__)
210
207
211 ipython_cmd = get_ipython_cmd()
208 ipython_cmd = get_ipython_cmd()
212 # Absolute path for filename
209 # Absolute path for filename
213 full_fname = os.path.join(test_dir, fname)
210 full_fname = os.path.join(test_dir, fname)
214 if pipe:
211 full_cmd = ipython_cmd + cmdargs + [full_fname]
215 full_cmd = ipython_cmd + cmdargs
212 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
216 p = Popen(full_cmd, stdin=open(full_fname), stdout=PIPE, stderr=PIPE)
217 else:
218 full_cmd = ipython_cmd + cmdargs + [full_fname]
219 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
220 out, err = p.communicate()
213 out, err = p.communicate()
221 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)
222 # `import readline` causes 'ESC[?1034h' to be output sometimes,
215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
223 # so strip that out before doing comparisons
216 # so strip that out before doing comparisons
224 if out:
217 if out:
225 out = re.sub(r'\x1b\[[^h]+h', '', out)
218 out = re.sub(r'\x1b\[[^h]+h', '', out)
226 return out, err
219 return out, err
227
220
228
221
229 def ipexec_validate(fname, expected_out, expected_err='',
222 def ipexec_validate(fname, expected_out, expected_err='',
230 options=None):
223 options=None):
231 """Utility to call 'ipython filename' and validate output/error.
224 """Utility to call 'ipython filename' and validate output/error.
232
225
233 This function raises an AssertionError if the validation fails.
226 This function raises an AssertionError if the validation fails.
234
227
235 Note that this starts IPython in a subprocess!
228 Note that this starts IPython in a subprocess!
236
229
237 Parameters
230 Parameters
238 ----------
231 ----------
239 fname : str
232 fname : str
240 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).
241
234
242 expected_out : str
235 expected_out : str
243 Expected stdout of the process.
236 Expected stdout of the process.
244
237
245 expected_err : optional, str
238 expected_err : optional, str
246 Expected stderr of the process.
239 Expected stderr of the process.
247
240
248 options : optional, list
241 options : optional, list
249 Extra command-line flags to be passed to IPython.
242 Extra command-line flags to be passed to IPython.
250
243
251 Returns
244 Returns
252 -------
245 -------
253 None
246 None
254 """
247 """
255
248
256 import nose.tools as nt
249 import nose.tools as nt
257
250
258 out, err = ipexec(fname, options)
251 out, err = ipexec(fname, options)
259 #print 'OUT', out # dbg
252 #print 'OUT', out # dbg
260 #print 'ERR', err # dbg
253 #print 'ERR', err # dbg
261 # 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
262 # more informative than simply having an empty stdout.
255 # more informative than simply having an empty stdout.
263 if err:
256 if err:
264 if expected_err:
257 if expected_err:
265 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()))
266 else:
259 else:
267 raise ValueError('Running file %r produced error: %r' %
260 raise ValueError('Running file %r produced error: %r' %
268 (fname, err))
261 (fname, err))
269 # If no errors or output on stderr was expected, match stdout
262 # If no errors or output on stderr was expected, match stdout
270 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()))
271
264
272
265
273 class TempFileMixin(object):
266 class TempFileMixin(object):
274 """Utility class to create temporary Python/IPython files.
267 """Utility class to create temporary Python/IPython files.
275
268
276 Meant as a mixin class for test cases."""
269 Meant as a mixin class for test cases."""
277
270
278 def mktmp(self, src, ext='.py'):
271 def mktmp(self, src, ext='.py'):
279 """Make a valid python temp file."""
272 """Make a valid python temp file."""
280 fname, f = temp_pyfile(src, ext)
273 fname, f = temp_pyfile(src, ext)
281 self.tmpfile = f
274 self.tmpfile = f
282 self.fname = fname
275 self.fname = fname
283
276
284 def tearDown(self):
277 def tearDown(self):
285 if hasattr(self, 'tmpfile'):
278 if hasattr(self, 'tmpfile'):
286 # 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
287 # win32, there's nothing to cleanup.
280 # win32, there's nothing to cleanup.
288 self.tmpfile.close()
281 self.tmpfile.close()
289 try:
282 try:
290 os.unlink(self.fname)
283 os.unlink(self.fname)
291 except:
284 except:
292 # 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
293 # delete it. I have no clue why
286 # delete it. I have no clue why
294 pass
287 pass
295
288
296 pair_fail_msg = ("Testing {0}\n\n"
289 pair_fail_msg = ("Testing {0}\n\n"
297 "In:\n"
290 "In:\n"
298 " {1!r}\n"
291 " {1!r}\n"
299 "Expected:\n"
292 "Expected:\n"
300 " {2!r}\n"
293 " {2!r}\n"
301 "Got:\n"
294 "Got:\n"
302 " {3!r}\n")
295 " {3!r}\n")
303 def check_pairs(func, pairs):
296 def check_pairs(func, pairs):
304 """Utility function for the common case of checking a function with a
297 """Utility function for the common case of checking a function with a
305 sequence of input/output pairs.
298 sequence of input/output pairs.
306
299
307 Parameters
300 Parameters
308 ----------
301 ----------
309 func : callable
302 func : callable
310 The function to be tested. Should accept a single argument.
303 The function to be tested. Should accept a single argument.
311 pairs : iterable
304 pairs : iterable
312 A list of (input, expected_output) tuples.
305 A list of (input, expected_output) tuples.
313
306
314 Returns
307 Returns
315 -------
308 -------
316 None. Raises an AssertionError if any output does not match the expected
309 None. Raises an AssertionError if any output does not match the expected
317 value.
310 value.
318 """
311 """
319 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
312 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
320 for inp, expected in pairs:
313 for inp, expected in pairs:
321 out = func(inp)
314 out = func(inp)
322 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
315 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
323
316
324
317
325 if py3compat.PY3:
318 if py3compat.PY3:
326 MyStringIO = StringIO
319 MyStringIO = StringIO
327 else:
320 else:
328 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
321 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
329 # so we need a class that can handle both.
322 # so we need a class that can handle both.
330 class MyStringIO(StringIO):
323 class MyStringIO(StringIO):
331 def write(self, s):
324 def write(self, s):
332 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
325 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
333 super(MyStringIO, self).write(s)
326 super(MyStringIO, self).write(s)
334
327
335 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
328 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
336 -------
329 -------
337 {2!s}
330 {2!s}
338 -------
331 -------
339 """
332 """
340
333
341 class AssertPrints(object):
334 class AssertPrints(object):
342 """Context manager for testing that code prints certain text.
335 """Context manager for testing that code prints certain text.
343
336
344 Examples
337 Examples
345 --------
338 --------
346 >>> with AssertPrints("abc", suppress=False):
339 >>> with AssertPrints("abc", suppress=False):
347 ... print("abcd")
340 ... print("abcd")
348 ... print("def")
341 ... print("def")
349 ...
342 ...
350 abcd
343 abcd
351 def
344 def
352 """
345 """
353 def __init__(self, s, channel='stdout', suppress=True):
346 def __init__(self, s, channel='stdout', suppress=True):
354 self.s = s
347 self.s = s
355 if isinstance(self.s, py3compat.string_types):
348 if isinstance(self.s, py3compat.string_types):
356 self.s = [self.s]
349 self.s = [self.s]
357 self.channel = channel
350 self.channel = channel
358 self.suppress = suppress
351 self.suppress = suppress
359
352
360 def __enter__(self):
353 def __enter__(self):
361 self.orig_stream = getattr(sys, self.channel)
354 self.orig_stream = getattr(sys, self.channel)
362 self.buffer = MyStringIO()
355 self.buffer = MyStringIO()
363 self.tee = Tee(self.buffer, channel=self.channel)
356 self.tee = Tee(self.buffer, channel=self.channel)
364 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
357 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
365
358
366 def __exit__(self, etype, value, traceback):
359 def __exit__(self, etype, value, traceback):
367 if value is not None:
360 if value is not None:
368 # If an error was raised, don't check anything else
361 # If an error was raised, don't check anything else
369 return False
362 return False
370 self.tee.flush()
363 self.tee.flush()
371 setattr(sys, self.channel, self.orig_stream)
364 setattr(sys, self.channel, self.orig_stream)
372 printed = self.buffer.getvalue()
365 printed = self.buffer.getvalue()
373 for s in self.s:
366 for s in self.s:
374 assert s in printed, notprinted_msg.format(s, self.channel, printed)
367 assert s in printed, notprinted_msg.format(s, self.channel, printed)
375 return False
368 return False
376
369
377 printed_msg = """Found {0!r} in printed output (on {1}):
370 printed_msg = """Found {0!r} in printed output (on {1}):
378 -------
371 -------
379 {2!s}
372 {2!s}
380 -------
373 -------
381 """
374 """
382
375
383 class AssertNotPrints(AssertPrints):
376 class AssertNotPrints(AssertPrints):
384 """Context manager for checking that certain output *isn't* produced.
377 """Context manager for checking that certain output *isn't* produced.
385
378
386 Counterpart of AssertPrints"""
379 Counterpart of AssertPrints"""
387 def __exit__(self, etype, value, traceback):
380 def __exit__(self, etype, value, traceback):
388 if value is not None:
381 if value is not None:
389 # If an error was raised, don't check anything else
382 # If an error was raised, don't check anything else
390 return False
383 return False
391 self.tee.flush()
384 self.tee.flush()
392 setattr(sys, self.channel, self.orig_stream)
385 setattr(sys, self.channel, self.orig_stream)
393 printed = self.buffer.getvalue()
386 printed = self.buffer.getvalue()
394 for s in self.s:
387 for s in self.s:
395 assert s not in printed, printed_msg.format(s, self.channel, printed)
388 assert s not in printed, printed_msg.format(s, self.channel, printed)
396 return False
389 return False
397
390
398 @contextmanager
391 @contextmanager
399 def mute_warn():
392 def mute_warn():
400 from IPython.utils import warn
393 from IPython.utils import warn
401 save_warn = warn.warn
394 save_warn = warn.warn
402 warn.warn = lambda *a, **kw: None
395 warn.warn = lambda *a, **kw: None
403 try:
396 try:
404 yield
397 yield
405 finally:
398 finally:
406 warn.warn = save_warn
399 warn.warn = save_warn
407
400
408 @contextmanager
401 @contextmanager
409 def make_tempfile(name):
402 def make_tempfile(name):
410 """ Create an empty, named, temporary file for the duration of the context.
403 """ Create an empty, named, temporary file for the duration of the context.
411 """
404 """
412 f = open(name, 'w')
405 f = open(name, 'w')
413 f.close()
406 f.close()
414 try:
407 try:
415 yield
408 yield
416 finally:
409 finally:
417 os.unlink(name)
410 os.unlink(name)
418
411
419
412
420 @contextmanager
413 @contextmanager
421 def monkeypatch(obj, name, attr):
414 def monkeypatch(obj, name, attr):
422 """
415 """
423 Context manager to replace attribute named `name` in `obj` with `attr`.
416 Context manager to replace attribute named `name` in `obj` with `attr`.
424 """
417 """
425 orig = getattr(obj, name)
418 orig = getattr(obj, name)
426 setattr(obj, name, attr)
419 setattr(obj, name, attr)
427 yield
420 yield
428 setattr(obj, name, orig)
421 setattr(obj, name, orig)
429
422
430
423
431 def help_output_test(subcommand=''):
424 def help_output_test(subcommand=''):
432 """test that `ipython [subcommand] -h` works"""
425 """test that `ipython [subcommand] -h` works"""
433 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
426 cmd = ' '.join(get_ipython_cmd() + [subcommand, '-h'])
434 out, err, rc = get_output_error_code(cmd)
427 out, err, rc = get_output_error_code(cmd)
435 nt.assert_equal(rc, 0, err)
428 nt.assert_equal(rc, 0, err)
436 nt.assert_not_in("Traceback", err)
429 nt.assert_not_in("Traceback", err)
437 nt.assert_in("Options", out)
430 nt.assert_in("Options", out)
438 nt.assert_in("--help-all", out)
431 nt.assert_in("--help-all", out)
439 return out, err
432 return out, err
440
433
441
434
442 def help_all_output_test(subcommand=''):
435 def help_all_output_test(subcommand=''):
443 """test that `ipython [subcommand] --help-all` works"""
436 """test that `ipython [subcommand] --help-all` works"""
444 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
437 cmd = ' '.join(get_ipython_cmd() + [subcommand, '--help-all'])
445 out, err, rc = get_output_error_code(cmd)
438 out, err, rc = get_output_error_code(cmd)
446 nt.assert_equal(rc, 0, err)
439 nt.assert_equal(rc, 0, err)
447 nt.assert_not_in("Traceback", err)
440 nt.assert_not_in("Traceback", err)
448 nt.assert_in("Options", out)
441 nt.assert_in("Options", out)
449 nt.assert_in("Class parameters", out)
442 nt.assert_in("Class parameters", out)
450 return out, err
443 return out, err
451
444
@@ -1,229 +1,234 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import os
17 import os
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 from .capture import CapturedIO, capture_output
20 from .capture import CapturedIO, capture_output
21 from .py3compat import string_types, input
21 from .py3compat import string_types, input
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Code
24 # Code
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class IOStream:
28 class IOStream:
29
29
30 def __init__(self,stream, fallback=None):
30 def __init__(self,stream, fallback=None):
31 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
31 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
32 if fallback is not None:
32 if fallback is not None:
33 stream = fallback
33 stream = fallback
34 else:
34 else:
35 raise ValueError("fallback required, but not specified")
35 raise ValueError("fallback required, but not specified")
36 self.stream = stream
36 self.stream = stream
37 self._swrite = stream.write
37 self._swrite = stream.write
38
38
39 # clone all methods not overridden:
39 # clone all methods not overridden:
40 def clone(meth):
40 def clone(meth):
41 return not hasattr(self, meth) and not meth.startswith('_')
41 return not hasattr(self, meth) and not meth.startswith('_')
42 for meth in filter(clone, dir(stream)):
42 for meth in filter(clone, dir(stream)):
43 setattr(self, meth, getattr(stream, meth))
43 setattr(self, meth, getattr(stream, meth))
44
44
45 def __repr__(self):
46 cls = self.__class__
47 tpl = '{mod}.{cls}({args})'
48 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
49
45 def write(self,data):
50 def write(self,data):
46 try:
51 try:
47 self._swrite(data)
52 self._swrite(data)
48 except:
53 except:
49 try:
54 try:
50 # print handles some unicode issues which may trip a plain
55 # print handles some unicode issues which may trip a plain
51 # write() call. Emulate write() by using an empty end
56 # write() call. Emulate write() by using an empty end
52 # argument.
57 # argument.
53 print(data, end='', file=self.stream)
58 print(data, end='', file=self.stream)
54 except:
59 except:
55 # if we get here, something is seriously broken.
60 # if we get here, something is seriously broken.
56 print('ERROR - failed to write data to stream:', self.stream,
61 print('ERROR - failed to write data to stream:', self.stream,
57 file=sys.stderr)
62 file=sys.stderr)
58
63
59 def writelines(self, lines):
64 def writelines(self, lines):
60 if isinstance(lines, string_types):
65 if isinstance(lines, string_types):
61 lines = [lines]
66 lines = [lines]
62 for line in lines:
67 for line in lines:
63 self.write(line)
68 self.write(line)
64
69
65 # This class used to have a writeln method, but regular files and streams
70 # This class used to have a writeln method, but regular files and streams
66 # in Python don't have this method. We need to keep this completely
71 # in Python don't have this method. We need to keep this completely
67 # compatible so we removed it.
72 # compatible so we removed it.
68
73
69 @property
74 @property
70 def closed(self):
75 def closed(self):
71 return self.stream.closed
76 return self.stream.closed
72
77
73 def close(self):
78 def close(self):
74 pass
79 pass
75
80
76 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
81 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
77 devnull = open(os.devnull, 'a')
82 devnull = open(os.devnull, 'a')
78 stdin = IOStream(sys.stdin, fallback=devnull)
83 stdin = IOStream(sys.stdin, fallback=devnull)
79 stdout = IOStream(sys.stdout, fallback=devnull)
84 stdout = IOStream(sys.stdout, fallback=devnull)
80 stderr = IOStream(sys.stderr, fallback=devnull)
85 stderr = IOStream(sys.stderr, fallback=devnull)
81
86
82 class IOTerm:
87 class IOTerm:
83 """ Term holds the file or file-like objects for handling I/O operations.
88 """ Term holds the file or file-like objects for handling I/O operations.
84
89
85 These are normally just sys.stdin, sys.stdout and sys.stderr but for
90 These are normally just sys.stdin, sys.stdout and sys.stderr but for
86 Windows they can can replaced to allow editing the strings before they are
91 Windows they can can replaced to allow editing the strings before they are
87 displayed."""
92 displayed."""
88
93
89 # In the future, having IPython channel all its I/O operations through
94 # In the future, having IPython channel all its I/O operations through
90 # this class will make it easier to embed it into other environments which
95 # this class will make it easier to embed it into other environments which
91 # are not a normal terminal (such as a GUI-based shell)
96 # are not a normal terminal (such as a GUI-based shell)
92 def __init__(self, stdin=None, stdout=None, stderr=None):
97 def __init__(self, stdin=None, stdout=None, stderr=None):
93 mymodule = sys.modules[__name__]
98 mymodule = sys.modules[__name__]
94 self.stdin = IOStream(stdin, mymodule.stdin)
99 self.stdin = IOStream(stdin, mymodule.stdin)
95 self.stdout = IOStream(stdout, mymodule.stdout)
100 self.stdout = IOStream(stdout, mymodule.stdout)
96 self.stderr = IOStream(stderr, mymodule.stderr)
101 self.stderr = IOStream(stderr, mymodule.stderr)
97
102
98
103
99 class Tee(object):
104 class Tee(object):
100 """A class to duplicate an output stream to stdout/err.
105 """A class to duplicate an output stream to stdout/err.
101
106
102 This works in a manner very similar to the Unix 'tee' command.
107 This works in a manner very similar to the Unix 'tee' command.
103
108
104 When the object is closed or deleted, it closes the original file given to
109 When the object is closed or deleted, it closes the original file given to
105 it for duplication.
110 it for duplication.
106 """
111 """
107 # Inspired by:
112 # Inspired by:
108 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
113 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
109
114
110 def __init__(self, file_or_name, mode="w", channel='stdout'):
115 def __init__(self, file_or_name, mode="w", channel='stdout'):
111 """Construct a new Tee object.
116 """Construct a new Tee object.
112
117
113 Parameters
118 Parameters
114 ----------
119 ----------
115 file_or_name : filename or open filehandle (writable)
120 file_or_name : filename or open filehandle (writable)
116 File that will be duplicated
121 File that will be duplicated
117
122
118 mode : optional, valid mode for open().
123 mode : optional, valid mode for open().
119 If a filename was give, open with this mode.
124 If a filename was give, open with this mode.
120
125
121 channel : str, one of ['stdout', 'stderr']
126 channel : str, one of ['stdout', 'stderr']
122 """
127 """
123 if channel not in ['stdout', 'stderr']:
128 if channel not in ['stdout', 'stderr']:
124 raise ValueError('Invalid channel spec %s' % channel)
129 raise ValueError('Invalid channel spec %s' % channel)
125
130
126 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
131 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
127 self.file = file_or_name
132 self.file = file_or_name
128 else:
133 else:
129 self.file = open(file_or_name, mode)
134 self.file = open(file_or_name, mode)
130 self.channel = channel
135 self.channel = channel
131 self.ostream = getattr(sys, channel)
136 self.ostream = getattr(sys, channel)
132 setattr(sys, channel, self)
137 setattr(sys, channel, self)
133 self._closed = False
138 self._closed = False
134
139
135 def close(self):
140 def close(self):
136 """Close the file and restore the channel."""
141 """Close the file and restore the channel."""
137 self.flush()
142 self.flush()
138 setattr(sys, self.channel, self.ostream)
143 setattr(sys, self.channel, self.ostream)
139 self.file.close()
144 self.file.close()
140 self._closed = True
145 self._closed = True
141
146
142 def write(self, data):
147 def write(self, data):
143 """Write data to both channels."""
148 """Write data to both channels."""
144 self.file.write(data)
149 self.file.write(data)
145 self.ostream.write(data)
150 self.ostream.write(data)
146 self.ostream.flush()
151 self.ostream.flush()
147
152
148 def flush(self):
153 def flush(self):
149 """Flush both channels."""
154 """Flush both channels."""
150 self.file.flush()
155 self.file.flush()
151 self.ostream.flush()
156 self.ostream.flush()
152
157
153 def __del__(self):
158 def __del__(self):
154 if not self._closed:
159 if not self._closed:
155 self.close()
160 self.close()
156
161
157
162
158 def ask_yes_no(prompt,default=None):
163 def ask_yes_no(prompt,default=None):
159 """Asks a question and returns a boolean (y/n) answer.
164 """Asks a question and returns a boolean (y/n) answer.
160
165
161 If default is given (one of 'y','n'), it is used if the user input is
166 If default is given (one of 'y','n'), it is used if the user input is
162 empty. Otherwise the question is repeated until an answer is given.
167 empty. Otherwise the question is repeated until an answer is given.
163
168
164 An EOF is treated as the default answer. If there is no default, an
169 An EOF is treated as the default answer. If there is no default, an
165 exception is raised to prevent infinite loops.
170 exception is raised to prevent infinite loops.
166
171
167 Valid answers are: y/yes/n/no (match is not case sensitive)."""
172 Valid answers are: y/yes/n/no (match is not case sensitive)."""
168
173
169 answers = {'y':True,'n':False,'yes':True,'no':False}
174 answers = {'y':True,'n':False,'yes':True,'no':False}
170 ans = None
175 ans = None
171 while ans not in answers.keys():
176 while ans not in answers.keys():
172 try:
177 try:
173 ans = input(prompt+' ').lower()
178 ans = input(prompt+' ').lower()
174 if not ans: # response was an empty string
179 if not ans: # response was an empty string
175 ans = default
180 ans = default
176 except KeyboardInterrupt:
181 except KeyboardInterrupt:
177 pass
182 pass
178 except EOFError:
183 except EOFError:
179 if default in answers.keys():
184 if default in answers.keys():
180 ans = default
185 ans = default
181 print()
186 print()
182 else:
187 else:
183 raise
188 raise
184
189
185 return answers[ans]
190 return answers[ans]
186
191
187
192
188 def temp_pyfile(src, ext='.py'):
193 def temp_pyfile(src, ext='.py'):
189 """Make a temporary python file, return filename and filehandle.
194 """Make a temporary python file, return filename and filehandle.
190
195
191 Parameters
196 Parameters
192 ----------
197 ----------
193 src : string or list of strings (no need for ending newlines if list)
198 src : string or list of strings (no need for ending newlines if list)
194 Source code to be written to the file.
199 Source code to be written to the file.
195
200
196 ext : optional, string
201 ext : optional, string
197 Extension for the generated file.
202 Extension for the generated file.
198
203
199 Returns
204 Returns
200 -------
205 -------
201 (filename, open filehandle)
206 (filename, open filehandle)
202 It is the caller's responsibility to close the open file and unlink it.
207 It is the caller's responsibility to close the open file and unlink it.
203 """
208 """
204 fname = tempfile.mkstemp(ext)[1]
209 fname = tempfile.mkstemp(ext)[1]
205 f = open(fname,'w')
210 f = open(fname,'w')
206 f.write(src)
211 f.write(src)
207 f.flush()
212 f.flush()
208 return fname, f
213 return fname, f
209
214
210
215
211 def raw_print(*args, **kw):
216 def raw_print(*args, **kw):
212 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
217 """Raw print to sys.__stdout__, otherwise identical interface to print()."""
213
218
214 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
219 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
215 file=sys.__stdout__)
220 file=sys.__stdout__)
216 sys.__stdout__.flush()
221 sys.__stdout__.flush()
217
222
218
223
219 def raw_print_err(*args, **kw):
224 def raw_print_err(*args, **kw):
220 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
225 """Raw print to sys.__stderr__, otherwise identical interface to print()."""
221
226
222 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
227 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
223 file=sys.__stderr__)
228 file=sys.__stderr__)
224 sys.__stderr__.flush()
229 sys.__stderr__.flush()
225
230
226
231
227 # Short aliases for quick debugging, do NOT use these in production code.
232 # Short aliases for quick debugging, do NOT use these in production code.
228 rprint = raw_print
233 rprint = raw_print
229 rprinte = raw_print_err
234 rprinte = raw_print_err
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now