##// END OF EJS Templates
Merge pull request #11792 from Carreau/textiowrapperleak...
Matthias Bussonnier -
r25107:4f349e0f merge
parent child Browse files
Show More
@@ -1,136 +1,135
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for testing.tools
3 Tests for testing.tools
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
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import unittest
18 import unittest
19
19
20 import nose.tools as nt
20 import nose.tools as nt
21
21
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.testing import tools as tt
23 from IPython.testing import tools as tt
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Tests
26 # Tests
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 @dec.skip_win32
29 @dec.skip_win32
30 def test_full_path_posix():
30 def test_full_path_posix():
31 spath = '/foo/bar.py'
31 spath = '/foo/bar.py'
32 result = tt.full_path(spath,['a.txt','b.txt'])
32 result = tt.full_path(spath,['a.txt','b.txt'])
33 nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt'])
33 nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt'])
34 spath = '/foo'
34 spath = '/foo'
35 result = tt.full_path(spath,['a.txt','b.txt'])
35 result = tt.full_path(spath,['a.txt','b.txt'])
36 nt.assert_equal(result, ['/a.txt', '/b.txt'])
36 nt.assert_equal(result, ['/a.txt', '/b.txt'])
37 result = tt.full_path(spath,'a.txt')
37 result = tt.full_path(spath,'a.txt')
38 nt.assert_equal(result, ['/a.txt'])
38 nt.assert_equal(result, ['/a.txt'])
39
39
40
40
41 @dec.skip_if_not_win32
41 @dec.skip_if_not_win32
42 def test_full_path_win32():
42 def test_full_path_win32():
43 spath = 'c:\\foo\\bar.py'
43 spath = 'c:\\foo\\bar.py'
44 result = tt.full_path(spath,['a.txt','b.txt'])
44 result = tt.full_path(spath,['a.txt','b.txt'])
45 nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt'])
45 nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt'])
46 spath = 'c:\\foo'
46 spath = 'c:\\foo'
47 result = tt.full_path(spath,['a.txt','b.txt'])
47 result = tt.full_path(spath,['a.txt','b.txt'])
48 nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt'])
48 nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt'])
49 result = tt.full_path(spath,'a.txt')
49 result = tt.full_path(spath,'a.txt')
50 nt.assert_equal(result, ['c:\\a.txt'])
50 nt.assert_equal(result, ['c:\\a.txt'])
51
51
52
52
53 def test_parser():
53 def test_parser():
54 err = ("FAILED (errors=1)", 1, 0)
54 err = ("FAILED (errors=1)", 1, 0)
55 fail = ("FAILED (failures=1)", 0, 1)
55 fail = ("FAILED (failures=1)", 0, 1)
56 both = ("FAILED (errors=1, failures=1)", 1, 1)
56 both = ("FAILED (errors=1, failures=1)", 1, 1)
57 for txt, nerr, nfail in [err, fail, both]:
57 for txt, nerr, nfail in [err, fail, both]:
58 nerr1, nfail1 = tt.parse_test_output(txt)
58 nerr1, nfail1 = tt.parse_test_output(txt)
59 nt.assert_equal(nerr, nerr1)
59 nt.assert_equal(nerr, nerr1)
60 nt.assert_equal(nfail, nfail1)
60 nt.assert_equal(nfail, nfail1)
61
61
62
62
63 def test_temp_pyfile():
63 def test_temp_pyfile():
64 src = 'pass\n'
64 src = 'pass\n'
65 fname, fh = tt.temp_pyfile(src)
65 fname = tt.temp_pyfile(src)
66 assert os.path.isfile(fname)
66 assert os.path.isfile(fname)
67 fh.close()
68 with open(fname) as fh2:
67 with open(fname) as fh2:
69 src2 = fh2.read()
68 src2 = fh2.read()
70 nt.assert_equal(src2, src)
69 nt.assert_equal(src2, src)
71
70
72 class TestAssertPrints(unittest.TestCase):
71 class TestAssertPrints(unittest.TestCase):
73 def test_passing(self):
72 def test_passing(self):
74 with tt.AssertPrints("abc"):
73 with tt.AssertPrints("abc"):
75 print("abcd")
74 print("abcd")
76 print("def")
75 print("def")
77 print(b"ghi")
76 print(b"ghi")
78
77
79 def test_failing(self):
78 def test_failing(self):
80 def func():
79 def func():
81 with tt.AssertPrints("abc"):
80 with tt.AssertPrints("abc"):
82 print("acd")
81 print("acd")
83 print("def")
82 print("def")
84 print(b"ghi")
83 print(b"ghi")
85
84
86 self.assertRaises(AssertionError, func)
85 self.assertRaises(AssertionError, func)
87
86
88
87
89 class Test_ipexec_validate(unittest.TestCase, tt.TempFileMixin):
88 class Test_ipexec_validate(unittest.TestCase, tt.TempFileMixin):
90 def test_main_path(self):
89 def test_main_path(self):
91 """Test with only stdout results.
90 """Test with only stdout results.
92 """
91 """
93 self.mktmp("print('A')\n"
92 self.mktmp("print('A')\n"
94 "print('B')\n"
93 "print('B')\n"
95 )
94 )
96 out = "A\nB"
95 out = "A\nB"
97 tt.ipexec_validate(self.fname, out)
96 tt.ipexec_validate(self.fname, out)
98
97
99 def test_main_path2(self):
98 def test_main_path2(self):
100 """Test with only stdout results, expecting windows line endings.
99 """Test with only stdout results, expecting windows line endings.
101 """
100 """
102 self.mktmp("print('A')\n"
101 self.mktmp("print('A')\n"
103 "print('B')\n"
102 "print('B')\n"
104 )
103 )
105 out = "A\r\nB"
104 out = "A\r\nB"
106 tt.ipexec_validate(self.fname, out)
105 tt.ipexec_validate(self.fname, out)
107
106
108 def test_exception_path(self):
107 def test_exception_path(self):
109 """Test exception path in exception_validate.
108 """Test exception path in exception_validate.
110 """
109 """
111 self.mktmp("import sys\n"
110 self.mktmp("import sys\n"
112 "print('A')\n"
111 "print('A')\n"
113 "print('B')\n"
112 "print('B')\n"
114 "print('C', file=sys.stderr)\n"
113 "print('C', file=sys.stderr)\n"
115 "print('D', file=sys.stderr)\n"
114 "print('D', file=sys.stderr)\n"
116 )
115 )
117 out = "A\nB"
116 out = "A\nB"
118 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\nD")
117 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\nD")
119
118
120 def test_exception_path2(self):
119 def test_exception_path2(self):
121 """Test exception path in exception_validate, expecting windows line endings.
120 """Test exception path in exception_validate, expecting windows line endings.
122 """
121 """
123 self.mktmp("import sys\n"
122 self.mktmp("import sys\n"
124 "print('A')\n"
123 "print('A')\n"
125 "print('B')\n"
124 "print('B')\n"
126 "print('C', file=sys.stderr)\n"
125 "print('C', file=sys.stderr)\n"
127 "print('D', file=sys.stderr)\n"
126 "print('D', file=sys.stderr)\n"
128 )
127 )
129 out = "A\r\nB"
128 out = "A\r\nB"
130 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\r\nD")
129 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\r\nD")
131
130
132
131
133 def tearDown(self):
132 def tearDown(self):
134 # tear down correctly the mixin,
133 # tear down correctly the mixin,
135 # unittest.TestCase.tearDown does nothing
134 # unittest.TestCase.tearDown does nothing
136 tt.TempFileMixin.tearDown(self)
135 tt.TempFileMixin.tearDown(self)
@@ -1,471 +1,470
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 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15 import tempfile
15 import tempfile
16
16
17 from contextlib import contextmanager
17 from contextlib import contextmanager
18 from io import StringIO
18 from io import StringIO
19 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from unittest.mock import patch
20 from unittest.mock import patch
21
21
22 try:
22 try:
23 # 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
24 # 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
25 # test suite, but NOT to use ipython itself.
25 # test suite, but NOT to use ipython itself.
26 import nose.tools as nt
26 import nose.tools as nt
27 has_nose = True
27 has_nose = True
28 except ImportError:
28 except ImportError:
29 has_nose = False
29 has_nose = False
30
30
31 from traitlets.config.loader import Config
31 from traitlets.config.loader import Config
32 from IPython.utils.process import get_output_error_code
32 from IPython.utils.process import get_output_error_code
33 from IPython.utils.text import list_strings
33 from IPython.utils.text import list_strings
34 from IPython.utils.io import temp_pyfile, Tee
34 from IPython.utils.io import temp_pyfile, Tee
35 from IPython.utils import py3compat
35 from IPython.utils import py3compat
36
36
37 from . import decorators as dec
37 from . import decorators as dec
38 from . import skipdoctest
38 from . import skipdoctest
39
39
40
40
41 # The docstring for full_path doctests differently on win32 (different path
41 # The docstring for full_path doctests differently on win32 (different path
42 # separator) so just skip the doctest there. The example remains informative.
42 # separator) so just skip the doctest there. The example remains informative.
43 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
43 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
44
44
45 @doctest_deco
45 @doctest_deco
46 def full_path(startPath,files):
46 def full_path(startPath,files):
47 """Make full paths for all the listed files, based on startPath.
47 """Make full paths for all the listed files, based on startPath.
48
48
49 Only the base part of startPath is kept, since this routine is typically
49 Only the base part of startPath is kept, since this routine is typically
50 used with a script's ``__file__`` variable as startPath. The base of startPath
50 used with a script's ``__file__`` variable as startPath. The base of startPath
51 is then prepended to all the listed files, forming the output list.
51 is then prepended to all the listed files, forming the output list.
52
52
53 Parameters
53 Parameters
54 ----------
54 ----------
55 startPath : string
55 startPath : string
56 Initial path to use as the base for the results. This path is split
56 Initial path to use as the base for the results. This path is split
57 using os.path.split() and only its first component is kept.
57 using os.path.split() and only its first component is kept.
58
58
59 files : string or list
59 files : string or list
60 One or more files.
60 One or more files.
61
61
62 Examples
62 Examples
63 --------
63 --------
64
64
65 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
65 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
66 ['/foo/a.txt', '/foo/b.txt']
66 ['/foo/a.txt', '/foo/b.txt']
67
67
68 >>> full_path('/foo',['a.txt','b.txt'])
68 >>> full_path('/foo',['a.txt','b.txt'])
69 ['/a.txt', '/b.txt']
69 ['/a.txt', '/b.txt']
70
70
71 If a single file is given, the output is still a list::
71 If a single file is given, the output is still a list::
72
72
73 >>> full_path('/foo','a.txt')
73 >>> full_path('/foo','a.txt')
74 ['/a.txt']
74 ['/a.txt']
75 """
75 """
76
76
77 files = list_strings(files)
77 files = list_strings(files)
78 base = os.path.split(startPath)[0]
78 base = os.path.split(startPath)[0]
79 return [ os.path.join(base,f) for f in files ]
79 return [ os.path.join(base,f) for f in files ]
80
80
81
81
82 def parse_test_output(txt):
82 def parse_test_output(txt):
83 """Parse the output of a test run and return errors, failures.
83 """Parse the output of a test run and return errors, failures.
84
84
85 Parameters
85 Parameters
86 ----------
86 ----------
87 txt : str
87 txt : str
88 Text output of a test run, assumed to contain a line of one of the
88 Text output of a test run, assumed to contain a line of one of the
89 following forms::
89 following forms::
90
90
91 'FAILED (errors=1)'
91 'FAILED (errors=1)'
92 'FAILED (failures=1)'
92 'FAILED (failures=1)'
93 'FAILED (errors=1, failures=1)'
93 'FAILED (errors=1, failures=1)'
94
94
95 Returns
95 Returns
96 -------
96 -------
97 nerr, nfail
97 nerr, nfail
98 number of errors and failures.
98 number of errors and failures.
99 """
99 """
100
100
101 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
101 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
102 if err_m:
102 if err_m:
103 nerr = int(err_m.group(1))
103 nerr = int(err_m.group(1))
104 nfail = 0
104 nfail = 0
105 return nerr, nfail
105 return nerr, nfail
106
106
107 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
107 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
108 if fail_m:
108 if fail_m:
109 nerr = 0
109 nerr = 0
110 nfail = int(fail_m.group(1))
110 nfail = int(fail_m.group(1))
111 return nerr, nfail
111 return nerr, nfail
112
112
113 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
113 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
114 re.MULTILINE)
114 re.MULTILINE)
115 if both_m:
115 if both_m:
116 nerr = int(both_m.group(1))
116 nerr = int(both_m.group(1))
117 nfail = int(both_m.group(2))
117 nfail = int(both_m.group(2))
118 return nerr, nfail
118 return nerr, nfail
119
119
120 # If the input didn't match any of these forms, assume no error/failures
120 # If the input didn't match any of these forms, assume no error/failures
121 return 0, 0
121 return 0, 0
122
122
123
123
124 # So nose doesn't think this is a test
124 # So nose doesn't think this is a test
125 parse_test_output.__test__ = False
125 parse_test_output.__test__ = False
126
126
127
127
128 def default_argv():
128 def default_argv():
129 """Return a valid default argv for creating testing instances of ipython"""
129 """Return a valid default argv for creating testing instances of ipython"""
130
130
131 return ['--quick', # so no config file is loaded
131 return ['--quick', # so no config file is loaded
132 # Other defaults to minimize side effects on stdout
132 # Other defaults to minimize side effects on stdout
133 '--colors=NoColor', '--no-term-title','--no-banner',
133 '--colors=NoColor', '--no-term-title','--no-banner',
134 '--autocall=0']
134 '--autocall=0']
135
135
136
136
137 def default_config():
137 def default_config():
138 """Return a config object with good defaults for testing."""
138 """Return a config object with good defaults for testing."""
139 config = Config()
139 config = Config()
140 config.TerminalInteractiveShell.colors = 'NoColor'
140 config.TerminalInteractiveShell.colors = 'NoColor'
141 config.TerminalTerminalInteractiveShell.term_title = False,
141 config.TerminalTerminalInteractiveShell.term_title = False,
142 config.TerminalInteractiveShell.autocall = 0
142 config.TerminalInteractiveShell.autocall = 0
143 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
143 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
144 config.HistoryManager.hist_file = f.name
144 config.HistoryManager.hist_file = f.name
145 f.close()
145 f.close()
146 config.HistoryManager.db_cache_size = 10000
146 config.HistoryManager.db_cache_size = 10000
147 return config
147 return config
148
148
149
149
150 def get_ipython_cmd(as_string=False):
150 def get_ipython_cmd(as_string=False):
151 """
151 """
152 Return appropriate IPython command line name. By default, this will return
152 Return appropriate IPython command line name. By default, this will return
153 a list that can be used with subprocess.Popen, for example, but passing
153 a list that can be used with subprocess.Popen, for example, but passing
154 `as_string=True` allows for returning the IPython command as a string.
154 `as_string=True` allows for returning the IPython command as a string.
155
155
156 Parameters
156 Parameters
157 ----------
157 ----------
158 as_string: bool
158 as_string: bool
159 Flag to allow to return the command as a string.
159 Flag to allow to return the command as a string.
160 """
160 """
161 ipython_cmd = [sys.executable, "-m", "IPython"]
161 ipython_cmd = [sys.executable, "-m", "IPython"]
162
162
163 if as_string:
163 if as_string:
164 ipython_cmd = " ".join(ipython_cmd)
164 ipython_cmd = " ".join(ipython_cmd)
165
165
166 return ipython_cmd
166 return ipython_cmd
167
167
168 def ipexec(fname, options=None, commands=()):
168 def ipexec(fname, options=None, commands=()):
169 """Utility to call 'ipython filename'.
169 """Utility to call 'ipython filename'.
170
170
171 Starts IPython with a minimal and safe configuration to make startup as fast
171 Starts IPython with a minimal and safe configuration to make startup as fast
172 as possible.
172 as possible.
173
173
174 Note that this starts IPython in a subprocess!
174 Note that this starts IPython in a subprocess!
175
175
176 Parameters
176 Parameters
177 ----------
177 ----------
178 fname : str
178 fname : str
179 Name of file to be executed (should have .py or .ipy extension).
179 Name of file to be executed (should have .py or .ipy extension).
180
180
181 options : optional, list
181 options : optional, list
182 Extra command-line flags to be passed to IPython.
182 Extra command-line flags to be passed to IPython.
183
183
184 commands : optional, list
184 commands : optional, list
185 Commands to send in on stdin
185 Commands to send in on stdin
186
186
187 Returns
187 Returns
188 -------
188 -------
189 ``(stdout, stderr)`` of ipython subprocess.
189 ``(stdout, stderr)`` of ipython subprocess.
190 """
190 """
191 if options is None: options = []
191 if options is None: options = []
192
192
193 cmdargs = default_argv() + options
193 cmdargs = default_argv() + options
194
194
195 test_dir = os.path.dirname(__file__)
195 test_dir = os.path.dirname(__file__)
196
196
197 ipython_cmd = get_ipython_cmd()
197 ipython_cmd = get_ipython_cmd()
198 # Absolute path for filename
198 # Absolute path for filename
199 full_fname = os.path.join(test_dir, fname)
199 full_fname = os.path.join(test_dir, fname)
200 full_cmd = ipython_cmd + cmdargs + [full_fname]
200 full_cmd = ipython_cmd + cmdargs + [full_fname]
201 env = os.environ.copy()
201 env = os.environ.copy()
202 # FIXME: ignore all warnings in ipexec while we have shims
202 # FIXME: ignore all warnings in ipexec while we have shims
203 # should we keep suppressing warnings here, even after removing shims?
203 # should we keep suppressing warnings here, even after removing shims?
204 env['PYTHONWARNINGS'] = 'ignore'
204 env['PYTHONWARNINGS'] = 'ignore'
205 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
205 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
206 for k, v in env.items():
206 for k, v in env.items():
207 # Debug a bizarre failure we've seen on Windows:
207 # Debug a bizarre failure we've seen on Windows:
208 # TypeError: environment can only contain strings
208 # TypeError: environment can only contain strings
209 if not isinstance(v, str):
209 if not isinstance(v, str):
210 print(k, v)
210 print(k, v)
211 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
211 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
212 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
212 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
213 out, err = py3compat.decode(out), py3compat.decode(err)
213 out, err = py3compat.decode(out), py3compat.decode(err)
214 # `import readline` causes 'ESC[?1034h' to be output sometimes,
214 # `import readline` causes 'ESC[?1034h' to be output sometimes,
215 # so strip that out before doing comparisons
215 # so strip that out before doing comparisons
216 if out:
216 if out:
217 out = re.sub(r'\x1b\[[^h]+h', '', out)
217 out = re.sub(r'\x1b\[[^h]+h', '', out)
218 return out, err
218 return out, err
219
219
220
220
221 def ipexec_validate(fname, expected_out, expected_err='',
221 def ipexec_validate(fname, expected_out, expected_err='',
222 options=None, commands=()):
222 options=None, commands=()):
223 """Utility to call 'ipython filename' and validate output/error.
223 """Utility to call 'ipython filename' and validate output/error.
224
224
225 This function raises an AssertionError if the validation fails.
225 This function raises an AssertionError if the validation fails.
226
226
227 Note that this starts IPython in a subprocess!
227 Note that this starts IPython in a subprocess!
228
228
229 Parameters
229 Parameters
230 ----------
230 ----------
231 fname : str
231 fname : str
232 Name of the file to be executed (should have .py or .ipy extension).
232 Name of the file to be executed (should have .py or .ipy extension).
233
233
234 expected_out : str
234 expected_out : str
235 Expected stdout of the process.
235 Expected stdout of the process.
236
236
237 expected_err : optional, str
237 expected_err : optional, str
238 Expected stderr of the process.
238 Expected stderr of the process.
239
239
240 options : optional, list
240 options : optional, list
241 Extra command-line flags to be passed to IPython.
241 Extra command-line flags to be passed to IPython.
242
242
243 Returns
243 Returns
244 -------
244 -------
245 None
245 None
246 """
246 """
247
247
248 import nose.tools as nt
248 import nose.tools as nt
249
249
250 out, err = ipexec(fname, options, commands)
250 out, err = ipexec(fname, options, commands)
251 #print 'OUT', out # dbg
251 #print 'OUT', out # dbg
252 #print 'ERR', err # dbg
252 #print 'ERR', err # dbg
253 # If there are any errors, we must check those before stdout, as they may be
253 # If there are any errors, we must check those before stdout, as they may be
254 # more informative than simply having an empty stdout.
254 # more informative than simply having an empty stdout.
255 if err:
255 if err:
256 if expected_err:
256 if expected_err:
257 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
257 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
258 else:
258 else:
259 raise ValueError('Running file %r produced error: %r' %
259 raise ValueError('Running file %r produced error: %r' %
260 (fname, err))
260 (fname, err))
261 # If no errors or output on stderr was expected, match stdout
261 # If no errors or output on stderr was expected, match stdout
262 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
262 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
263
263
264
264
265 class TempFileMixin(object):
265 class TempFileMixin(object):
266 """Utility class to create temporary Python/IPython files.
266 """Utility class to create temporary Python/IPython files.
267
267
268 Meant as a mixin class for test cases."""
268 Meant as a mixin class for test cases."""
269
269
270 def mktmp(self, src, ext='.py'):
270 def mktmp(self, src, ext='.py'):
271 """Make a valid python temp file."""
271 """Make a valid python temp file."""
272 fname, f = temp_pyfile(src, ext)
272 fname = temp_pyfile(src, ext)
273 if not hasattr(self, 'tmps'):
273 if not hasattr(self, 'tmps'):
274 self.tmps=[]
274 self.tmps=[]
275 self.tmps.append((f, fname))
275 self.tmps.append(fname)
276 self.fname = fname
276 self.fname = fname
277
277
278 def tearDown(self):
278 def tearDown(self):
279 # 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
280 # win32, there's nothing to cleanup.
280 # win32, there's nothing to cleanup.
281 if hasattr(self, 'tmps'):
281 if hasattr(self, 'tmps'):
282 for f,fname in self.tmps:
282 for fname in self.tmps:
283 # If the tmpfile wasn't made because of skipped tests, like in
283 # If the tmpfile wasn't made because of skipped tests, like in
284 # win32, there's nothing to cleanup.
284 # win32, there's nothing to cleanup.
285 f.close()
286 try:
285 try:
287 os.unlink(fname)
286 os.unlink(fname)
288 except:
287 except:
289 # On Windows, even though we close the file, we still can't
288 # On Windows, even though we close the file, we still can't
290 # delete it. I have no clue why
289 # delete it. I have no clue why
291 pass
290 pass
292
291
293 def __enter__(self):
292 def __enter__(self):
294 return self
293 return self
295
294
296 def __exit__(self, exc_type, exc_value, traceback):
295 def __exit__(self, exc_type, exc_value, traceback):
297 self.tearDown()
296 self.tearDown()
298
297
299
298
300 pair_fail_msg = ("Testing {0}\n\n"
299 pair_fail_msg = ("Testing {0}\n\n"
301 "In:\n"
300 "In:\n"
302 " {1!r}\n"
301 " {1!r}\n"
303 "Expected:\n"
302 "Expected:\n"
304 " {2!r}\n"
303 " {2!r}\n"
305 "Got:\n"
304 "Got:\n"
306 " {3!r}\n")
305 " {3!r}\n")
307 def check_pairs(func, pairs):
306 def check_pairs(func, pairs):
308 """Utility function for the common case of checking a function with a
307 """Utility function for the common case of checking a function with a
309 sequence of input/output pairs.
308 sequence of input/output pairs.
310
309
311 Parameters
310 Parameters
312 ----------
311 ----------
313 func : callable
312 func : callable
314 The function to be tested. Should accept a single argument.
313 The function to be tested. Should accept a single argument.
315 pairs : iterable
314 pairs : iterable
316 A list of (input, expected_output) tuples.
315 A list of (input, expected_output) tuples.
317
316
318 Returns
317 Returns
319 -------
318 -------
320 None. Raises an AssertionError if any output does not match the expected
319 None. Raises an AssertionError if any output does not match the expected
321 value.
320 value.
322 """
321 """
323 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
322 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
324 for inp, expected in pairs:
323 for inp, expected in pairs:
325 out = func(inp)
324 out = func(inp)
326 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
325 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
327
326
328
327
329 MyStringIO = StringIO
328 MyStringIO = StringIO
330
329
331 _re_type = type(re.compile(r''))
330 _re_type = type(re.compile(r''))
332
331
333 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
332 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
334 -------
333 -------
335 {2!s}
334 {2!s}
336 -------
335 -------
337 """
336 """
338
337
339 class AssertPrints(object):
338 class AssertPrints(object):
340 """Context manager for testing that code prints certain text.
339 """Context manager for testing that code prints certain text.
341
340
342 Examples
341 Examples
343 --------
342 --------
344 >>> with AssertPrints("abc", suppress=False):
343 >>> with AssertPrints("abc", suppress=False):
345 ... print("abcd")
344 ... print("abcd")
346 ... print("def")
345 ... print("def")
347 ...
346 ...
348 abcd
347 abcd
349 def
348 def
350 """
349 """
351 def __init__(self, s, channel='stdout', suppress=True):
350 def __init__(self, s, channel='stdout', suppress=True):
352 self.s = s
351 self.s = s
353 if isinstance(self.s, (str, _re_type)):
352 if isinstance(self.s, (str, _re_type)):
354 self.s = [self.s]
353 self.s = [self.s]
355 self.channel = channel
354 self.channel = channel
356 self.suppress = suppress
355 self.suppress = suppress
357
356
358 def __enter__(self):
357 def __enter__(self):
359 self.orig_stream = getattr(sys, self.channel)
358 self.orig_stream = getattr(sys, self.channel)
360 self.buffer = MyStringIO()
359 self.buffer = MyStringIO()
361 self.tee = Tee(self.buffer, channel=self.channel)
360 self.tee = Tee(self.buffer, channel=self.channel)
362 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
361 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
363
362
364 def __exit__(self, etype, value, traceback):
363 def __exit__(self, etype, value, traceback):
365 try:
364 try:
366 if value is not None:
365 if value is not None:
367 # If an error was raised, don't check anything else
366 # If an error was raised, don't check anything else
368 return False
367 return False
369 self.tee.flush()
368 self.tee.flush()
370 setattr(sys, self.channel, self.orig_stream)
369 setattr(sys, self.channel, self.orig_stream)
371 printed = self.buffer.getvalue()
370 printed = self.buffer.getvalue()
372 for s in self.s:
371 for s in self.s:
373 if isinstance(s, _re_type):
372 if isinstance(s, _re_type):
374 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
373 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
375 else:
374 else:
376 assert s in printed, notprinted_msg.format(s, self.channel, printed)
375 assert s in printed, notprinted_msg.format(s, self.channel, printed)
377 return False
376 return False
378 finally:
377 finally:
379 self.tee.close()
378 self.tee.close()
380
379
381 printed_msg = """Found {0!r} in printed output (on {1}):
380 printed_msg = """Found {0!r} in printed output (on {1}):
382 -------
381 -------
383 {2!s}
382 {2!s}
384 -------
383 -------
385 """
384 """
386
385
387 class AssertNotPrints(AssertPrints):
386 class AssertNotPrints(AssertPrints):
388 """Context manager for checking that certain output *isn't* produced.
387 """Context manager for checking that certain output *isn't* produced.
389
388
390 Counterpart of AssertPrints"""
389 Counterpart of AssertPrints"""
391 def __exit__(self, etype, value, traceback):
390 def __exit__(self, etype, value, traceback):
392 try:
391 try:
393 if value is not None:
392 if value is not None:
394 # If an error was raised, don't check anything else
393 # If an error was raised, don't check anything else
395 self.tee.close()
394 self.tee.close()
396 return False
395 return False
397 self.tee.flush()
396 self.tee.flush()
398 setattr(sys, self.channel, self.orig_stream)
397 setattr(sys, self.channel, self.orig_stream)
399 printed = self.buffer.getvalue()
398 printed = self.buffer.getvalue()
400 for s in self.s:
399 for s in self.s:
401 if isinstance(s, _re_type):
400 if isinstance(s, _re_type):
402 assert not s.search(printed),printed_msg.format(
401 assert not s.search(printed),printed_msg.format(
403 s.pattern, self.channel, printed)
402 s.pattern, self.channel, printed)
404 else:
403 else:
405 assert s not in printed, printed_msg.format(
404 assert s not in printed, printed_msg.format(
406 s, self.channel, printed)
405 s, self.channel, printed)
407 return False
406 return False
408 finally:
407 finally:
409 self.tee.close()
408 self.tee.close()
410
409
411 @contextmanager
410 @contextmanager
412 def mute_warn():
411 def mute_warn():
413 from IPython.utils import warn
412 from IPython.utils import warn
414 save_warn = warn.warn
413 save_warn = warn.warn
415 warn.warn = lambda *a, **kw: None
414 warn.warn = lambda *a, **kw: None
416 try:
415 try:
417 yield
416 yield
418 finally:
417 finally:
419 warn.warn = save_warn
418 warn.warn = save_warn
420
419
421 @contextmanager
420 @contextmanager
422 def make_tempfile(name):
421 def make_tempfile(name):
423 """ Create an empty, named, temporary file for the duration of the context.
422 """ Create an empty, named, temporary file for the duration of the context.
424 """
423 """
425 open(name, 'w').close()
424 open(name, 'w').close()
426 try:
425 try:
427 yield
426 yield
428 finally:
427 finally:
429 os.unlink(name)
428 os.unlink(name)
430
429
431 def fake_input(inputs):
430 def fake_input(inputs):
432 """Temporarily replace the input() function to return the given values
431 """Temporarily replace the input() function to return the given values
433
432
434 Use as a context manager:
433 Use as a context manager:
435
434
436 with fake_input(['result1', 'result2']):
435 with fake_input(['result1', 'result2']):
437 ...
436 ...
438
437
439 Values are returned in order. If input() is called again after the last value
438 Values are returned in order. If input() is called again after the last value
440 was used, EOFError is raised.
439 was used, EOFError is raised.
441 """
440 """
442 it = iter(inputs)
441 it = iter(inputs)
443 def mock_input(prompt=''):
442 def mock_input(prompt=''):
444 try:
443 try:
445 return next(it)
444 return next(it)
446 except StopIteration:
445 except StopIteration:
447 raise EOFError('No more inputs given')
446 raise EOFError('No more inputs given')
448
447
449 return patch('builtins.input', mock_input)
448 return patch('builtins.input', mock_input)
450
449
451 def help_output_test(subcommand=''):
450 def help_output_test(subcommand=''):
452 """test that `ipython [subcommand] -h` works"""
451 """test that `ipython [subcommand] -h` works"""
453 cmd = get_ipython_cmd() + [subcommand, '-h']
452 cmd = get_ipython_cmd() + [subcommand, '-h']
454 out, err, rc = get_output_error_code(cmd)
453 out, err, rc = get_output_error_code(cmd)
455 nt.assert_equal(rc, 0, err)
454 nt.assert_equal(rc, 0, err)
456 nt.assert_not_in("Traceback", err)
455 nt.assert_not_in("Traceback", err)
457 nt.assert_in("Options", out)
456 nt.assert_in("Options", out)
458 nt.assert_in("--help-all", out)
457 nt.assert_in("--help-all", out)
459 return out, err
458 return out, err
460
459
461
460
462 def help_all_output_test(subcommand=''):
461 def help_all_output_test(subcommand=''):
463 """test that `ipython [subcommand] --help-all` works"""
462 """test that `ipython [subcommand] --help-all` works"""
464 cmd = get_ipython_cmd() + [subcommand, '--help-all']
463 cmd = get_ipython_cmd() + [subcommand, '--help-all']
465 out, err, rc = get_output_error_code(cmd)
464 out, err, rc = get_output_error_code(cmd)
466 nt.assert_equal(rc, 0, err)
465 nt.assert_equal(rc, 0, err)
467 nt.assert_not_in("Traceback", err)
466 nt.assert_not_in("Traceback", err)
468 nt.assert_in("Options", out)
467 nt.assert_in("Options", out)
469 nt.assert_in("Class", out)
468 nt.assert_in("Class", out)
470 return out, err
469 return out, err
471
470
@@ -1,248 +1,248
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10
10
11 import atexit
11 import atexit
12 import os
12 import os
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import warnings
15 import warnings
16 from warnings import warn
16 from warnings import warn
17
17
18 from IPython.utils.decorators import undoc
18 from IPython.utils.decorators import undoc
19 from .capture import CapturedIO, capture_output
19 from .capture import CapturedIO, capture_output
20
20
21 @undoc
21 @undoc
22 class IOStream:
22 class IOStream:
23
23
24 def __init__(self, stream, fallback=None):
24 def __init__(self, stream, fallback=None):
25 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
25 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26 DeprecationWarning, stacklevel=2)
26 DeprecationWarning, stacklevel=2)
27 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
27 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28 if fallback is not None:
28 if fallback is not None:
29 stream = fallback
29 stream = fallback
30 else:
30 else:
31 raise ValueError("fallback required, but not specified")
31 raise ValueError("fallback required, but not specified")
32 self.stream = stream
32 self.stream = stream
33 self._swrite = stream.write
33 self._swrite = stream.write
34
34
35 # clone all methods not overridden:
35 # clone all methods not overridden:
36 def clone(meth):
36 def clone(meth):
37 return not hasattr(self, meth) and not meth.startswith('_')
37 return not hasattr(self, meth) and not meth.startswith('_')
38 for meth in filter(clone, dir(stream)):
38 for meth in filter(clone, dir(stream)):
39 try:
39 try:
40 val = getattr(stream, meth)
40 val = getattr(stream, meth)
41 except AttributeError:
41 except AttributeError:
42 pass
42 pass
43 else:
43 else:
44 setattr(self, meth, val)
44 setattr(self, meth, val)
45
45
46 def __repr__(self):
46 def __repr__(self):
47 cls = self.__class__
47 cls = self.__class__
48 tpl = '{mod}.{cls}({args})'
48 tpl = '{mod}.{cls}({args})'
49 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
49 return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50
50
51 def write(self,data):
51 def write(self,data):
52 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
52 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53 DeprecationWarning, stacklevel=2)
53 DeprecationWarning, stacklevel=2)
54 try:
54 try:
55 self._swrite(data)
55 self._swrite(data)
56 except:
56 except:
57 try:
57 try:
58 # print handles some unicode issues which may trip a plain
58 # print handles some unicode issues which may trip a plain
59 # write() call. Emulate write() by using an empty end
59 # write() call. Emulate write() by using an empty end
60 # argument.
60 # argument.
61 print(data, end='', file=self.stream)
61 print(data, end='', file=self.stream)
62 except:
62 except:
63 # if we get here, something is seriously broken.
63 # if we get here, something is seriously broken.
64 print('ERROR - failed to write data to stream:', self.stream,
64 print('ERROR - failed to write data to stream:', self.stream,
65 file=sys.stderr)
65 file=sys.stderr)
66
66
67 def writelines(self, lines):
67 def writelines(self, lines):
68 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
68 warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69 DeprecationWarning, stacklevel=2)
69 DeprecationWarning, stacklevel=2)
70 if isinstance(lines, str):
70 if isinstance(lines, str):
71 lines = [lines]
71 lines = [lines]
72 for line in lines:
72 for line in lines:
73 self.write(line)
73 self.write(line)
74
74
75 # This class used to have a writeln method, but regular files and streams
75 # This class used to have a writeln method, but regular files and streams
76 # in Python don't have this method. We need to keep this completely
76 # in Python don't have this method. We need to keep this completely
77 # compatible so we removed it.
77 # compatible so we removed it.
78
78
79 @property
79 @property
80 def closed(self):
80 def closed(self):
81 return self.stream.closed
81 return self.stream.closed
82
82
83 def close(self):
83 def close(self):
84 pass
84 pass
85
85
86 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
86 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87 devnull = open(os.devnull, 'w')
87 devnull = open(os.devnull, 'w')
88 atexit.register(devnull.close)
88 atexit.register(devnull.close)
89
89
90 # io.std* are deprecated, but don't show our own deprecation warnings
90 # io.std* are deprecated, but don't show our own deprecation warnings
91 # during initialization of the deprecated API.
91 # during initialization of the deprecated API.
92 with warnings.catch_warnings():
92 with warnings.catch_warnings():
93 warnings.simplefilter('ignore', DeprecationWarning)
93 warnings.simplefilter('ignore', DeprecationWarning)
94 stdin = IOStream(sys.stdin, fallback=devnull)
94 stdin = IOStream(sys.stdin, fallback=devnull)
95 stdout = IOStream(sys.stdout, fallback=devnull)
95 stdout = IOStream(sys.stdout, fallback=devnull)
96 stderr = IOStream(sys.stderr, fallback=devnull)
96 stderr = IOStream(sys.stderr, fallback=devnull)
97
97
98 class Tee(object):
98 class Tee(object):
99 """A class to duplicate an output stream to stdout/err.
99 """A class to duplicate an output stream to stdout/err.
100
100
101 This works in a manner very similar to the Unix 'tee' command.
101 This works in a manner very similar to the Unix 'tee' command.
102
102
103 When the object is closed or deleted, it closes the original file given to
103 When the object is closed or deleted, it closes the original file given to
104 it for duplication.
104 it for duplication.
105 """
105 """
106 # Inspired by:
106 # Inspired by:
107 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
107 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108
108
109 def __init__(self, file_or_name, mode="w", channel='stdout'):
109 def __init__(self, file_or_name, mode="w", channel='stdout'):
110 """Construct a new Tee object.
110 """Construct a new Tee object.
111
111
112 Parameters
112 Parameters
113 ----------
113 ----------
114 file_or_name : filename or open filehandle (writable)
114 file_or_name : filename or open filehandle (writable)
115 File that will be duplicated
115 File that will be duplicated
116
116
117 mode : optional, valid mode for open().
117 mode : optional, valid mode for open().
118 If a filename was give, open with this mode.
118 If a filename was give, open with this mode.
119
119
120 channel : str, one of ['stdout', 'stderr']
120 channel : str, one of ['stdout', 'stderr']
121 """
121 """
122 if channel not in ['stdout', 'stderr']:
122 if channel not in ['stdout', 'stderr']:
123 raise ValueError('Invalid channel spec %s' % channel)
123 raise ValueError('Invalid channel spec %s' % channel)
124
124
125 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
125 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126 self.file = file_or_name
126 self.file = file_or_name
127 else:
127 else:
128 self.file = open(file_or_name, mode)
128 self.file = open(file_or_name, mode)
129 self.channel = channel
129 self.channel = channel
130 self.ostream = getattr(sys, channel)
130 self.ostream = getattr(sys, channel)
131 setattr(sys, channel, self)
131 setattr(sys, channel, self)
132 self._closed = False
132 self._closed = False
133
133
134 def close(self):
134 def close(self):
135 """Close the file and restore the channel."""
135 """Close the file and restore the channel."""
136 self.flush()
136 self.flush()
137 setattr(sys, self.channel, self.ostream)
137 setattr(sys, self.channel, self.ostream)
138 self.file.close()
138 self.file.close()
139 self._closed = True
139 self._closed = True
140
140
141 def write(self, data):
141 def write(self, data):
142 """Write data to both channels."""
142 """Write data to both channels."""
143 self.file.write(data)
143 self.file.write(data)
144 self.ostream.write(data)
144 self.ostream.write(data)
145 self.ostream.flush()
145 self.ostream.flush()
146
146
147 def flush(self):
147 def flush(self):
148 """Flush both channels."""
148 """Flush both channels."""
149 self.file.flush()
149 self.file.flush()
150 self.ostream.flush()
150 self.ostream.flush()
151
151
152 def __del__(self):
152 def __del__(self):
153 if not self._closed:
153 if not self._closed:
154 self.close()
154 self.close()
155
155
156
156
157 def ask_yes_no(prompt, default=None, interrupt=None):
157 def ask_yes_no(prompt, default=None, interrupt=None):
158 """Asks a question and returns a boolean (y/n) answer.
158 """Asks a question and returns a boolean (y/n) answer.
159
159
160 If default is given (one of 'y','n'), it is used if the user input is
160 If default is given (one of 'y','n'), it is used if the user input is
161 empty. If interrupt is given (one of 'y','n'), it is used if the user
161 empty. If interrupt is given (one of 'y','n'), it is used if the user
162 presses Ctrl-C. Otherwise the question is repeated until an answer is
162 presses Ctrl-C. Otherwise the question is repeated until an answer is
163 given.
163 given.
164
164
165 An EOF is treated as the default answer. If there is no default, an
165 An EOF is treated as the default answer. If there is no default, an
166 exception is raised to prevent infinite loops.
166 exception is raised to prevent infinite loops.
167
167
168 Valid answers are: y/yes/n/no (match is not case sensitive)."""
168 Valid answers are: y/yes/n/no (match is not case sensitive)."""
169
169
170 answers = {'y':True,'n':False,'yes':True,'no':False}
170 answers = {'y':True,'n':False,'yes':True,'no':False}
171 ans = None
171 ans = None
172 while ans not in answers.keys():
172 while ans not in answers.keys():
173 try:
173 try:
174 ans = input(prompt+' ').lower()
174 ans = input(prompt+' ').lower()
175 if not ans: # response was an empty string
175 if not ans: # response was an empty string
176 ans = default
176 ans = default
177 except KeyboardInterrupt:
177 except KeyboardInterrupt:
178 if interrupt:
178 if interrupt:
179 ans = interrupt
179 ans = interrupt
180 print("\r")
180 print("\r")
181 except EOFError:
181 except EOFError:
182 if default in answers.keys():
182 if default in answers.keys():
183 ans = default
183 ans = default
184 print()
184 print()
185 else:
185 else:
186 raise
186 raise
187
187
188 return answers[ans]
188 return answers[ans]
189
189
190
190
191 def temp_pyfile(src, ext='.py'):
191 def temp_pyfile(src, ext='.py'):
192 """Make a temporary python file, return filename and filehandle.
192 """Make a temporary python file, return filename and filehandle.
193
193
194 Parameters
194 Parameters
195 ----------
195 ----------
196 src : string or list of strings (no need for ending newlines if list)
196 src : string or list of strings (no need for ending newlines if list)
197 Source code to be written to the file.
197 Source code to be written to the file.
198
198
199 ext : optional, string
199 ext : optional, string
200 Extension for the generated file.
200 Extension for the generated file.
201
201
202 Returns
202 Returns
203 -------
203 -------
204 (filename, open filehandle)
204 (filename, open filehandle)
205 It is the caller's responsibility to close the open file and unlink it.
205 It is the caller's responsibility to close the open file and unlink it.
206 """
206 """
207 fname = tempfile.mkstemp(ext)[1]
207 fname = tempfile.mkstemp(ext)[1]
208 f = open(fname,'w')
208 with open(fname,'w') as f:
209 f.write(src)
209 f.write(src)
210 f.flush()
210 f.flush()
211 return fname, f
211 return fname
212
212
213 @undoc
213 @undoc
214 def atomic_writing(*args, **kwargs):
214 def atomic_writing(*args, **kwargs):
215 """DEPRECATED: moved to notebook.services.contents.fileio"""
215 """DEPRECATED: moved to notebook.services.contents.fileio"""
216 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
216 warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
217 from notebook.services.contents.fileio import atomic_writing
217 from notebook.services.contents.fileio import atomic_writing
218 return atomic_writing(*args, **kwargs)
218 return atomic_writing(*args, **kwargs)
219
219
220 @undoc
220 @undoc
221 def raw_print(*args, **kw):
221 def raw_print(*args, **kw):
222 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
222 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
223 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
223 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
224
224
225 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
225 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
226 file=sys.__stdout__)
226 file=sys.__stdout__)
227 sys.__stdout__.flush()
227 sys.__stdout__.flush()
228
228
229 @undoc
229 @undoc
230 def raw_print_err(*args, **kw):
230 def raw_print_err(*args, **kw):
231 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
231 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
232 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
232 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
233
233
234 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
234 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
235 file=sys.__stderr__)
235 file=sys.__stderr__)
236 sys.__stderr__.flush()
236 sys.__stderr__.flush()
237
237
238 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
238 # used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
239 # Keep for a version or two then should remove
239 # Keep for a version or two then should remove
240 rprint = raw_print
240 rprint = raw_print
241 rprinte = raw_print_err
241 rprinte = raw_print_err
242
242
243 @undoc
243 @undoc
244 def unicode_std_stream(stream='stdout'):
244 def unicode_std_stream(stream='stdout'):
245 """DEPRECATED, moved to nbconvert.utils.io"""
245 """DEPRECATED, moved to nbconvert.utils.io"""
246 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
246 warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
247 from nbconvert.utils.io import unicode_std_stream
247 from nbconvert.utils.io import unicode_std_stream
248 return unicode_std_stream(stream)
248 return unicode_std_stream(stream)
@@ -1,105 +1,105
1 """
1 """
2 Tools to open .py files as Unicode, using the encoding specified within the file,
2 Tools to open .py files as Unicode, using the encoding specified within the file,
3 as per PEP 263.
3 as per PEP 263.
4
4
5 Much of the code is taken from the tokenize module in Python 3.2.
5 Much of the code is taken from the tokenize module in Python 3.2.
6 """
6 """
7
7
8 import io
8 import io
9 from io import TextIOWrapper, BytesIO
9 from io import TextIOWrapper, BytesIO
10 import re
10 import re
11 from tokenize import open, detect_encoding
11 from tokenize import open, detect_encoding
12
12
13 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE)
13 cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)", re.UNICODE)
14 cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE)
14 cookie_comment_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE)
15
15
16 def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True):
16 def source_to_unicode(txt, errors='replace', skip_encoding_cookie=True):
17 """Converts a bytes string with python source code to unicode.
17 """Converts a bytes string with python source code to unicode.
18
18
19 Unicode strings are passed through unchanged. Byte strings are checked
19 Unicode strings are passed through unchanged. Byte strings are checked
20 for the python source file encoding cookie to determine encoding.
20 for the python source file encoding cookie to determine encoding.
21 txt can be either a bytes buffer or a string containing the source
21 txt can be either a bytes buffer or a string containing the source
22 code.
22 code.
23 """
23 """
24 if isinstance(txt, str):
24 if isinstance(txt, str):
25 return txt
25 return txt
26 if isinstance(txt, bytes):
26 if isinstance(txt, bytes):
27 buffer = BytesIO(txt)
27 buffer = BytesIO(txt)
28 else:
28 else:
29 buffer = txt
29 buffer = txt
30 try:
30 try:
31 encoding, _ = detect_encoding(buffer.readline)
31 encoding, _ = detect_encoding(buffer.readline)
32 except SyntaxError:
32 except SyntaxError:
33 encoding = "ascii"
33 encoding = "ascii"
34 buffer.seek(0)
34 buffer.seek(0)
35 text = TextIOWrapper(buffer, encoding, errors=errors, line_buffering=True)
35 with TextIOWrapper(buffer, encoding, errors=errors, line_buffering=True) as text:
36 text.mode = 'r'
36 text.mode = 'r'
37 if skip_encoding_cookie:
37 if skip_encoding_cookie:
38 return u"".join(strip_encoding_cookie(text))
38 return u"".join(strip_encoding_cookie(text))
39 else:
39 else:
40 return text.read()
40 return text.read()
41
41
42 def strip_encoding_cookie(filelike):
42 def strip_encoding_cookie(filelike):
43 """Generator to pull lines from a text-mode file, skipping the encoding
43 """Generator to pull lines from a text-mode file, skipping the encoding
44 cookie if it is found in the first two lines.
44 cookie if it is found in the first two lines.
45 """
45 """
46 it = iter(filelike)
46 it = iter(filelike)
47 try:
47 try:
48 first = next(it)
48 first = next(it)
49 if not cookie_comment_re.match(first):
49 if not cookie_comment_re.match(first):
50 yield first
50 yield first
51 second = next(it)
51 second = next(it)
52 if not cookie_comment_re.match(second):
52 if not cookie_comment_re.match(second):
53 yield second
53 yield second
54 except StopIteration:
54 except StopIteration:
55 return
55 return
56
56
57 for line in it:
57 for line in it:
58 yield line
58 yield line
59
59
60 def read_py_file(filename, skip_encoding_cookie=True):
60 def read_py_file(filename, skip_encoding_cookie=True):
61 """Read a Python file, using the encoding declared inside the file.
61 """Read a Python file, using the encoding declared inside the file.
62
62
63 Parameters
63 Parameters
64 ----------
64 ----------
65 filename : str
65 filename : str
66 The path to the file to read.
66 The path to the file to read.
67 skip_encoding_cookie : bool
67 skip_encoding_cookie : bool
68 If True (the default), and the encoding declaration is found in the first
68 If True (the default), and the encoding declaration is found in the first
69 two lines, that line will be excluded from the output - compiling a
69 two lines, that line will be excluded from the output - compiling a
70 unicode string with an encoding declaration is a SyntaxError in Python 2.
70 unicode string with an encoding declaration is a SyntaxError in Python 2.
71
71
72 Returns
72 Returns
73 -------
73 -------
74 A unicode string containing the contents of the file.
74 A unicode string containing the contents of the file.
75 """
75 """
76 with open(filename) as f: # the open function defined in this module.
76 with open(filename) as f: # the open function defined in this module.
77 if skip_encoding_cookie:
77 if skip_encoding_cookie:
78 return "".join(strip_encoding_cookie(f))
78 return "".join(strip_encoding_cookie(f))
79 else:
79 else:
80 return f.read()
80 return f.read()
81
81
82 def read_py_url(url, errors='replace', skip_encoding_cookie=True):
82 def read_py_url(url, errors='replace', skip_encoding_cookie=True):
83 """Read a Python file from a URL, using the encoding declared inside the file.
83 """Read a Python file from a URL, using the encoding declared inside the file.
84
84
85 Parameters
85 Parameters
86 ----------
86 ----------
87 url : str
87 url : str
88 The URL from which to fetch the file.
88 The URL from which to fetch the file.
89 errors : str
89 errors : str
90 How to handle decoding errors in the file. Options are the same as for
90 How to handle decoding errors in the file. Options are the same as for
91 bytes.decode(), but here 'replace' is the default.
91 bytes.decode(), but here 'replace' is the default.
92 skip_encoding_cookie : bool
92 skip_encoding_cookie : bool
93 If True (the default), and the encoding declaration is found in the first
93 If True (the default), and the encoding declaration is found in the first
94 two lines, that line will be excluded from the output - compiling a
94 two lines, that line will be excluded from the output - compiling a
95 unicode string with an encoding declaration is a SyntaxError in Python 2.
95 unicode string with an encoding declaration is a SyntaxError in Python 2.
96
96
97 Returns
97 Returns
98 -------
98 -------
99 A unicode string containing the contents of the file.
99 A unicode string containing the contents of the file.
100 """
100 """
101 # Deferred import for faster start
101 # Deferred import for faster start
102 from urllib.request import urlopen
102 from urllib.request import urlopen
103 response = urlopen(url)
103 response = urlopen(url)
104 buffer = io.BytesIO(response.read())
104 buffer = io.BytesIO(response.read())
105 return source_to_unicode(buffer, errors, skip_encoding_cookie)
105 return source_to_unicode(buffer, errors, skip_encoding_cookie)
@@ -1,31 +1,39
1 import io
1 import io
2 import os.path
2 import os.path
3 import nose.tools as nt
3 import nose.tools as nt
4
4
5 from IPython.utils import openpy
5 from IPython.utils import openpy
6
6
7 mydir = os.path.dirname(__file__)
7 mydir = os.path.dirname(__file__)
8 nonascii_path = os.path.join(mydir, '../../core/tests/nonascii.py')
8 nonascii_path = os.path.join(mydir, "../../core/tests/nonascii.py")
9
9
10
10 def test_detect_encoding():
11 def test_detect_encoding():
11 with open(nonascii_path, 'rb') as f:
12 with open(nonascii_path, "rb") as f:
12 enc, lines = openpy.detect_encoding(f.readline)
13 enc, lines = openpy.detect_encoding(f.readline)
13 nt.assert_equal(enc, 'iso-8859-5')
14 nt.assert_equal(enc, "iso-8859-5")
15
14
16
15 def test_read_file():
17 def test_read_file():
16 read_specified_enc = io.open(nonascii_path, encoding='iso-8859-5').read()
18 with io.open(nonascii_path, encoding="iso-8859-5") as f:
19 read_specified_enc = f.read()
17 read_detected_enc = openpy.read_py_file(nonascii_path, skip_encoding_cookie=False)
20 read_detected_enc = openpy.read_py_file(nonascii_path, skip_encoding_cookie=False)
18 nt.assert_equal(read_detected_enc, read_specified_enc)
21 nt.assert_equal(read_detected_enc, read_specified_enc)
19 assert u'coding: iso-8859-5' in read_detected_enc
22 assert "coding: iso-8859-5" in read_detected_enc
20
23
21 read_strip_enc_cookie = openpy.read_py_file(nonascii_path, skip_encoding_cookie=True)
24 read_strip_enc_cookie = openpy.read_py_file(
22 assert u'coding: iso-8859-5' not in read_strip_enc_cookie
25 nonascii_path, skip_encoding_cookie=True
26 )
27 assert "coding: iso-8859-5" not in read_strip_enc_cookie
28
23
29
24 def test_source_to_unicode():
30 def test_source_to_unicode():
25 with io.open(nonascii_path, 'rb') as f:
31 with io.open(nonascii_path, "rb") as f:
26 source_bytes = f.read()
32 source_bytes = f.read()
27 nt.assert_equal(openpy.source_to_unicode(source_bytes, skip_encoding_cookie=False).splitlines(),
33 nt.assert_equal(
28 source_bytes.decode('iso-8859-5').splitlines())
34 openpy.source_to_unicode(source_bytes, skip_encoding_cookie=False).splitlines(),
35 source_bytes.decode("iso-8859-5").splitlines(),
36 )
29
37
30 source_no_cookie = openpy.source_to_unicode(source_bytes, skip_encoding_cookie=True)
38 source_no_cookie = openpy.source_to_unicode(source_bytes, skip_encoding_cookie=True)
31 nt.assert_not_in(u'coding: iso-8859-5', source_no_cookie)
39 nt.assert_not_in("coding: iso-8859-5", source_no_cookie)
General Comments 0
You need to be logged in to leave comments. Login now