##// END OF EJS Templates
Follow Fernando's suggestions.
Thomas Kluyver -
Show More
@@ -1,332 +1,337 b''
1 1 # encoding: utf-8
2 2 """
3 3 Paging capabilities for IPython.core
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez
9 9
10 10 Notes
11 11 -----
12 12
13 13 For now this uses ipapi, so it can't be in IPython.utils. If we can get
14 14 rid of that dependency, we could move it there.
15 15 -----
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Copyright (C) 2008-2009 The IPython Development Team
20 20 #
21 21 # Distributed under the terms of the BSD License. The full license is in
22 22 # the file COPYING, distributed as part of this software.
23 23 #-----------------------------------------------------------------------------
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Imports
27 27 #-----------------------------------------------------------------------------
28 28
29 29 import os
30 30 import re
31 31 import sys
32 32 import tempfile
33 33
34 34 from IPython.core import ipapi
35 35 from IPython.core.error import TryNext
36 36 from IPython.utils.cursesimport import use_curses
37 37 from IPython.utils.data import chop
38 38 from IPython.utils import io
39 39 from IPython.utils.process import system
40 40 from IPython.utils.terminal import get_terminal_size
41 41
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Classes and functions
45 45 #-----------------------------------------------------------------------------
46 46
47 47 esc_re = re.compile(r"(\x1b[^m]+m)")
48 48
49 49 def page_dumb(strng, start=0, screen_lines=25):
50 50 """Very dumb 'pager' in Python, for when nothing else works.
51 51
52 52 Only moves forward, same interface as page(), except for pager_cmd and
53 53 mode."""
54 54
55 55 out_ln = strng.splitlines()[start:]
56 56 screens = chop(out_ln,screen_lines-1)
57 57 if len(screens) == 1:
58 58 print >>io.stdout, os.linesep.join(screens[0])
59 59 else:
60 60 last_escape = ""
61 61 for scr in screens[0:-1]:
62 62 hunk = os.linesep.join(scr)
63 63 print >>io.stdout, last_escape + hunk
64 64 if not page_more():
65 65 return
66 66 esc_list = esc_re.findall(hunk)
67 67 if len(esc_list) > 0:
68 68 last_escape = esc_list[-1]
69 69 print >>io.stdout, last_escape + os.linesep.join(screens[-1])
70 70
71 71 def _detect_screen_size(use_curses, screen_lines_def):
72 """Attempt to work out the number of lines on the screen.
73
74 This is called by page(). It can raise an error (e.g. when run in the
75 test suite), so it's separated out so it can easily be called in a try block.
76 """
72 77 if (TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5':
73 78 local_use_curses = use_curses
74 79 else:
75 80 # curses causes problems on many terminals other than xterm, and
76 81 # some termios calls lock up on Sun OS5.
77 82 local_use_curses = False
78 83 if local_use_curses:
79 84 import termios
80 85 import curses
81 86 # There is a bug in curses, where *sometimes* it fails to properly
82 87 # initialize, and then after the endwin() call is made, the
83 88 # terminal is left in an unusable state. Rather than trying to
84 89 # check everytime for this (by requesting and comparing termios
85 90 # flags each time), we just save the initial terminal state and
86 91 # unconditionally reset it every time. It's cheaper than making
87 92 # the checks.
88 93 term_flags = termios.tcgetattr(sys.stdout)
89 94
90 95 # Curses modifies the stdout buffer size by default, which messes
91 96 # up Python's normal stdout buffering. This would manifest itself
92 97 # to IPython users as delayed printing on stdout after having used
93 98 # the pager.
94 99 #
95 100 # We can prevent this by manually setting the NCURSES_NO_SETBUF
96 101 # environment variable. For more details, see:
97 102 # http://bugs.python.org/issue10144
98 103 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
99 104 os.environ['NCURSES_NO_SETBUF'] = ''
100 105
101 106 # Proceed with curses initialization
102 107 scr = curses.initscr()
103 108 screen_lines_real,screen_cols = scr.getmaxyx()
104 109 curses.endwin()
105 110
106 111 # Restore environment
107 112 if NCURSES_NO_SETBUF is None:
108 113 del os.environ['NCURSES_NO_SETBUF']
109 114 else:
110 115 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
111 116
112 117 # Restore terminal state in case endwin() didn't.
113 118 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
114 119 # Now we have what we needed: the screen size in rows/columns
115 120 return screen_lines_real
116 121 #print '***Screen size:',screen_lines_real,'lines x',\
117 122 #screen_cols,'columns.' # dbg
118 123 else:
119 124 return screen_lines_def
120 125
121 126 def page(strng, start=0, screen_lines=0, pager_cmd=None):
122 127 """Print a string, piping through a pager after a certain length.
123 128
124 129 The screen_lines parameter specifies the number of *usable* lines of your
125 130 terminal screen (total lines minus lines you need to reserve to show other
126 131 information).
127 132
128 133 If you set screen_lines to a number <=0, page() will try to auto-determine
129 134 your screen size and will only use up to (screen_size+screen_lines) for
130 135 printing, paging after that. That is, if you want auto-detection but need
131 136 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
132 137 auto-detection without any lines reserved simply use screen_lines = 0.
133 138
134 139 If a string won't fit in the allowed lines, it is sent through the
135 140 specified pager command. If none given, look for PAGER in the environment,
136 141 and ultimately default to less.
137 142
138 143 If no system pager works, the string is sent through a 'dumb pager'
139 144 written in python, very simplistic.
140 145 """
141 146
142 147 # Some routines may auto-compute start offsets incorrectly and pass a
143 148 # negative value. Offset to 0 for robustness.
144 149 start = max(0, start)
145 150
146 151 # first, try the hook
147 152 ip = ipapi.get()
148 153 if ip:
149 154 try:
150 155 ip.hooks.show_in_pager(strng)
151 156 return
152 157 except TryNext:
153 158 pass
154 159
155 160 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 161 TERM = os.environ.get('TERM','dumb')
157 162 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 163 print strng
159 164 return
160 165 # chop off the topmost part of the string we don't want to see
161 166 str_lines = strng.splitlines()[start:]
162 167 str_toprint = os.linesep.join(str_lines)
163 168 num_newlines = len(str_lines)
164 169 len_str = len(str_toprint)
165 170
166 171 # Dumb heuristics to guesstimate number of on-screen lines the string
167 172 # takes. Very basic, but good enough for docstrings in reasonable
168 173 # terminals. If someone later feels like refining it, it's not hard.
169 174 numlines = max(num_newlines,int(len_str/80)+1)
170 175
171 176 screen_lines_def = get_terminal_size()[1]
172 177
173 178 # auto-determine screen size
174 179 if screen_lines <= 0:
175 180 try:
176 181 screen_lines += _detect_screen_size(use_curses, screen_lines_def)
177 182 except Exception:
178 183 print >>io.stdout, str_toprint
179 184 return
180 185
181 186 #print 'numlines',numlines,'screenlines',screen_lines # dbg
182 187 if numlines <= screen_lines :
183 188 #print '*** normal print' # dbg
184 189 print >>io.stdout, str_toprint
185 190 else:
186 191 # Try to open pager and default to internal one if that fails.
187 192 # All failure modes are tagged as 'retval=1', to match the return
188 193 # value of a failed system command. If any intermediate attempt
189 194 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 195 pager_cmd = get_pager_cmd(pager_cmd)
191 196 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 197 if os.name == 'nt':
193 198 if pager_cmd.startswith('type'):
194 199 # The default WinXP 'type' command is failing on complex strings.
195 200 retval = 1
196 201 else:
197 202 tmpname = tempfile.mktemp('.txt')
198 203 tmpfile = file(tmpname,'wt')
199 204 tmpfile.write(strng)
200 205 tmpfile.close()
201 206 cmd = "%s < %s" % (pager_cmd,tmpname)
202 207 if os.system(cmd):
203 208 retval = 1
204 209 else:
205 210 retval = None
206 211 os.remove(tmpname)
207 212 else:
208 213 try:
209 214 retval = None
210 215 # if I use popen4, things hang. No idea why.
211 216 #pager,shell_out = os.popen4(pager_cmd)
212 217 pager = os.popen(pager_cmd,'w')
213 218 pager.write(strng)
214 219 pager.close()
215 220 retval = pager.close() # success returns None
216 221 except IOError,msg: # broken pipe when user quits
217 222 if msg.args == (32,'Broken pipe'):
218 223 retval = None
219 224 else:
220 225 retval = 1
221 226 except OSError:
222 227 # Other strange problems, sometimes seen in Win2k/cygwin
223 228 retval = 1
224 229 if retval is not None:
225 230 page_dumb(strng,screen_lines=screen_lines)
226 231
227 232
228 233 def page_file(fname, start=0, pager_cmd=None):
229 234 """Page a file, using an optional pager command and starting line.
230 235 """
231 236
232 237 pager_cmd = get_pager_cmd(pager_cmd)
233 238 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
234 239
235 240 try:
236 241 if os.environ['TERM'] in ['emacs','dumb']:
237 242 raise EnvironmentError
238 243 system(pager_cmd + ' ' + fname)
239 244 except:
240 245 try:
241 246 if start > 0:
242 247 start -= 1
243 248 page(open(fname).read(),start)
244 249 except:
245 250 print 'Unable to show file',`fname`
246 251
247 252
248 253 def get_pager_cmd(pager_cmd=None):
249 254 """Return a pager command.
250 255
251 256 Makes some attempts at finding an OS-correct one.
252 257 """
253 258 if os.name == 'posix':
254 259 default_pager_cmd = 'less -r' # -r for color control sequences
255 260 elif os.name in ['nt','dos']:
256 261 default_pager_cmd = 'type'
257 262
258 263 if pager_cmd is None:
259 264 try:
260 265 pager_cmd = os.environ['PAGER']
261 266 except:
262 267 pager_cmd = default_pager_cmd
263 268 return pager_cmd
264 269
265 270
266 271 def get_pager_start(pager, start):
267 272 """Return the string for paging files with an offset.
268 273
269 274 This is the '+N' argument which less and more (under Unix) accept.
270 275 """
271 276
272 277 if pager in ['less','more']:
273 278 if start:
274 279 start_string = '+' + str(start)
275 280 else:
276 281 start_string = ''
277 282 else:
278 283 start_string = ''
279 284 return start_string
280 285
281 286
282 287 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
283 288 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
284 289 import msvcrt
285 290 def page_more():
286 291 """ Smart pausing between pages
287 292
288 293 @return: True if need print more lines, False if quit
289 294 """
290 295 io.stdout.write('---Return to continue, q to quit--- ')
291 296 ans = msvcrt.getch()
292 297 if ans in ("q", "Q"):
293 298 result = False
294 299 else:
295 300 result = True
296 301 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
297 302 return result
298 303 else:
299 304 def page_more():
300 305 ans = raw_input('---Return to continue, q to quit--- ')
301 306 if ans.lower().startswith('q'):
302 307 return False
303 308 else:
304 309 return True
305 310
306 311
307 312 def snip_print(str,width = 75,print_full = 0,header = ''):
308 313 """Print a string snipping the midsection to fit in width.
309 314
310 315 print_full: mode control:
311 316 - 0: only snip long strings
312 317 - 1: send to page() directly.
313 318 - 2: snip long strings and ask for full length viewing with page()
314 319 Return 1 if snipping was necessary, 0 otherwise."""
315 320
316 321 if print_full == 1:
317 322 page(header+str)
318 323 return 0
319 324
320 325 print header,
321 326 if len(str) < width:
322 327 print str
323 328 snip = 0
324 329 else:
325 330 whalf = int((width -5)/2)
326 331 print str[:whalf] + ' <...> ' + str[-whalf:]
327 332 snip = 1
328 333 if snip and print_full == 2:
329 334 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
330 335 page(str)
331 336 return snip
332 337
@@ -1,400 +1,402 b''
1 1 """Generic testing tools that do NOT depend on Twisted.
2 2
3 3 In particular, this module exposes a set of top-level assert* functions that
4 4 can be used in place of nose.tools.assert* in method generators (the ones in
5 5 nose can not, at least as of nose 0.10.4).
6 6
7 7 Note: our testing package contains testing.util, which does depend on Twisted
8 8 and provides utilities for tests that manage Deferreds. All testing support
9 9 tools that only depend on nose, IPython or the standard library should go here
10 10 instead.
11 11
12 12
13 13 Authors
14 14 -------
15 15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 16 """
17 17
18 18 from __future__ import absolute_import
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Copyright (C) 2009 The IPython Development Team
22 22 #
23 23 # Distributed under the terms of the BSD License. The full license is in
24 24 # the file COPYING, distributed as part of this software.
25 25 #-----------------------------------------------------------------------------
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Imports
29 29 #-----------------------------------------------------------------------------
30 30
31 31 import os
32 32 import re
33 33 import sys
34 34 import tempfile
35 35
36 36 from contextlib import contextmanager
37 37 from io import StringIO
38 38
39 39 try:
40 40 # These tools are used by parts of the runtime, so we make the nose
41 41 # dependency optional at this point. Nose is a hard dependency to run the
42 42 # test suite, but NOT to use ipython itself.
43 43 import nose.tools as nt
44 44 has_nose = True
45 45 except ImportError:
46 46 has_nose = False
47 47
48 48 from IPython.config.loader import Config
49 49 from IPython.utils.process import find_cmd, getoutputerror
50 50 from IPython.utils.text import list_strings, getdefaultencoding
51 51 from IPython.utils.io import temp_pyfile, Tee
52 52 from IPython.utils import py3compat
53 53
54 54 from . import decorators as dec
55 55 from . import skipdoctest
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Globals
59 59 #-----------------------------------------------------------------------------
60 60
61 61 # Make a bunch of nose.tools assert wrappers that can be used in test
62 62 # generators. This will expose an assert* function for each one in nose.tools.
63 63
64 64 _tpl = """
65 65 def %(name)s(*a,**kw):
66 66 return nt.%(name)s(*a,**kw)
67 67 """
68 68
69 69 if has_nose:
70 70 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 71 exec _tpl % dict(name=_x)
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Functions and classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77 # The docstring for full_path doctests differently on win32 (different path
78 78 # separator) so just skip the doctest there. The example remains informative.
79 79 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80 80
81 81 @doctest_deco
82 82 def full_path(startPath,files):
83 83 """Make full paths for all the listed files, based on startPath.
84 84
85 85 Only the base part of startPath is kept, since this routine is typically
86 86 used with a script's __file__ variable as startPath. The base of startPath
87 87 is then prepended to all the listed files, forming the output list.
88 88
89 89 Parameters
90 90 ----------
91 91 startPath : string
92 92 Initial path to use as the base for the results. This path is split
93 93 using os.path.split() and only its first component is kept.
94 94
95 95 files : string or list
96 96 One or more files.
97 97
98 98 Examples
99 99 --------
100 100
101 101 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 102 ['/foo/a.txt', '/foo/b.txt']
103 103
104 104 >>> full_path('/foo',['a.txt','b.txt'])
105 105 ['/a.txt', '/b.txt']
106 106
107 107 If a single file is given, the output is still a list:
108 108 >>> full_path('/foo','a.txt')
109 109 ['/a.txt']
110 110 """
111 111
112 112 files = list_strings(files)
113 113 base = os.path.split(startPath)[0]
114 114 return [ os.path.join(base,f) for f in files ]
115 115
116 116
117 117 def parse_test_output(txt):
118 118 """Parse the output of a test run and return errors, failures.
119 119
120 120 Parameters
121 121 ----------
122 122 txt : str
123 123 Text output of a test run, assumed to contain a line of one of the
124 124 following forms::
125 125 'FAILED (errors=1)'
126 126 'FAILED (failures=1)'
127 127 'FAILED (errors=1, failures=1)'
128 128
129 129 Returns
130 130 -------
131 131 nerr, nfail: number of errors and failures.
132 132 """
133 133
134 134 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 135 if err_m:
136 136 nerr = int(err_m.group(1))
137 137 nfail = 0
138 138 return nerr, nfail
139 139
140 140 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 141 if fail_m:
142 142 nerr = 0
143 143 nfail = int(fail_m.group(1))
144 144 return nerr, nfail
145 145
146 146 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 147 re.MULTILINE)
148 148 if both_m:
149 149 nerr = int(both_m.group(1))
150 150 nfail = int(both_m.group(2))
151 151 return nerr, nfail
152 152
153 153 # If the input didn't match any of these forms, assume no error/failures
154 154 return 0, 0
155 155
156 156
157 157 # So nose doesn't think this is a test
158 158 parse_test_output.__test__ = False
159 159
160 160
161 161 def default_argv():
162 162 """Return a valid default argv for creating testing instances of ipython"""
163 163
164 164 return ['--quick', # so no config file is loaded
165 165 # Other defaults to minimize side effects on stdout
166 166 '--colors=NoColor', '--no-term-title','--no-banner',
167 167 '--autocall=0']
168 168
169 169
170 170 def default_config():
171 171 """Return a config object with good defaults for testing."""
172 172 config = Config()
173 173 config.TerminalInteractiveShell.colors = 'NoColor'
174 174 config.TerminalTerminalInteractiveShell.term_title = False,
175 175 config.TerminalInteractiveShell.autocall = 0
176 176 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 177 config.HistoryManager.db_cache_size = 10000
178 178 return config
179 179
180 180
181 181 def ipexec(fname, options=None):
182 182 """Utility to call 'ipython filename'.
183 183
184 184 Starts IPython witha minimal and safe configuration to make startup as fast
185 185 as possible.
186 186
187 187 Note that this starts IPython in a subprocess!
188 188
189 189 Parameters
190 190 ----------
191 191 fname : str
192 192 Name of file to be executed (should have .py or .ipy extension).
193 193
194 194 options : optional, list
195 195 Extra command-line flags to be passed to IPython.
196 196
197 197 Returns
198 198 -------
199 199 (stdout, stderr) of ipython subprocess.
200 200 """
201 201 if options is None: options = []
202 202
203 203 # For these subprocess calls, eliminate all prompt printing so we only see
204 204 # output from script execution
205 205 prompt_opts = [ '--InteractiveShell.prompt_in1=""',
206 206 '--InteractiveShell.prompt_in2=""',
207 207 '--InteractiveShell.prompt_out=""'
208 208 ]
209 209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210 210
211 211 _ip = get_ipython()
212 212 test_dir = os.path.dirname(__file__)
213 213
214 214 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 215 # Absolute path for filename
216 216 full_fname = os.path.join(test_dir, fname)
217 217 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 218 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 219 out = getoutputerror(full_cmd)
220 220 # `import readline` causes 'ESC[?1034h' to be the first output sometimes,
221 221 # so strip that off the front of the first line if it is found
222 222 if out:
223 223 first = out[0]
224 224 m = re.match(r'\x1b\[[^h]+h', first)
225 225 if m:
226 226 # strip initial readline escape
227 227 out = list(out)
228 228 out[0] = first[len(m.group()):]
229 229 out = tuple(out)
230 230 return out
231 231
232 232
233 233 def ipexec_validate(fname, expected_out, expected_err='',
234 234 options=None):
235 235 """Utility to call 'ipython filename' and validate output/error.
236 236
237 237 This function raises an AssertionError if the validation fails.
238 238
239 239 Note that this starts IPython in a subprocess!
240 240
241 241 Parameters
242 242 ----------
243 243 fname : str
244 244 Name of the file to be executed (should have .py or .ipy extension).
245 245
246 246 expected_out : str
247 247 Expected stdout of the process.
248 248
249 249 expected_err : optional, str
250 250 Expected stderr of the process.
251 251
252 252 options : optional, list
253 253 Extra command-line flags to be passed to IPython.
254 254
255 255 Returns
256 256 -------
257 257 None
258 258 """
259 259
260 260 import nose.tools as nt
261 261
262 262 out, err = ipexec(fname)
263 263 #print 'OUT', out # dbg
264 264 #print 'ERR', err # dbg
265 265 # If there are any errors, we must check those befor stdout, as they may be
266 266 # more informative than simply having an empty stdout.
267 267 if err:
268 268 if expected_err:
269 269 nt.assert_equals(err.strip(), expected_err.strip())
270 270 else:
271 271 raise ValueError('Running file %r produced error: %r' %
272 272 (fname, err))
273 273 # If no errors or output on stderr was expected, match stdout
274 274 nt.assert_equals(out.strip(), expected_out.strip())
275 275
276 276
277 277 class TempFileMixin(object):
278 278 """Utility class to create temporary Python/IPython files.
279 279
280 280 Meant as a mixin class for test cases."""
281 281
282 282 def mktmp(self, src, ext='.py'):
283 283 """Make a valid python temp file."""
284 284 fname, f = temp_pyfile(src, ext)
285 285 self.tmpfile = f
286 286 self.fname = fname
287 287
288 288 def tearDown(self):
289 289 if hasattr(self, 'tmpfile'):
290 290 # If the tmpfile wasn't made because of skipped tests, like in
291 291 # win32, there's nothing to cleanup.
292 292 self.tmpfile.close()
293 293 try:
294 294 os.unlink(self.fname)
295 295 except:
296 296 # On Windows, even though we close the file, we still can't
297 297 # delete it. I have no clue why
298 298 pass
299 299
300 300 pair_fail_msg = ("Testing {0}\n\n"
301 301 "In:\n"
302 302 " {1!r}\n"
303 303 "Expected:\n"
304 304 " {2!r}\n"
305 305 "Got:\n"
306 306 " {3!r}\n")
307 307 def check_pairs(func, pairs):
308 308 """Utility function for the common case of checking a function with a
309 309 sequence of input/output pairs.
310 310
311 311 Parameters
312 312 ----------
313 313 func : callable
314 314 The function to be tested. Should accept a single argument.
315 315 pairs : iterable
316 316 A list of (input, expected_output) tuples.
317 317
318 318 Returns
319 319 -------
320 320 None. Raises an AssertionError if any output does not match the expected
321 321 value.
322 322 """
323 323 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
324 324 for inp, expected in pairs:
325 325 out = func(inp)
326 326 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
327 327
328
328 329 if py3compat.PY3:
329 330 MyStringIO = StringIO
330 331 else:
331 332 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
332 333 # so we need a class that can handle both.
333 334 class MyStringIO(StringIO):
334 335 def write(self, s):
335 336 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
336 337 super(MyStringIO, self).write(s)
337 338
338 339 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
339 340 {2!r}"""
341
340 342 class AssertPrints(object):
341 343 """Context manager for testing that code prints certain text.
342 344
343 345 Examples
344 346 --------
345 347 >>> with AssertPrints("abc", suppress=False):
346 348 ... print "abcd"
347 349 ... print "def"
348 350 ...
349 351 abcd
350 352 def
351 353 """
352 354 def __init__(self, s, channel='stdout', suppress=True):
353 355 self.s = s
354 356 self.channel = channel
355 357 self.suppress = suppress
356 358
357 359 def __enter__(self):
358 360 self.orig_stream = getattr(sys, self.channel)
359 361 self.buffer = MyStringIO()
360 362 self.tee = Tee(self.buffer, channel=self.channel)
361 363 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
362 364
363 365 def __exit__(self, etype, value, traceback):
364 366 self.tee.flush()
365 367 setattr(sys, self.channel, self.orig_stream)
366 368 printed = self.buffer.getvalue()
367 369 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
368 370 return False
369 371
370 372 class AssertNotPrints(AssertPrints):
371 373 """Context manager for checking that certain output *isn't* produced.
372 374
373 375 Counterpart of AssertPrints"""
374 376 def __exit__(self, etype, value, traceback):
375 377 self.tee.flush()
376 378 setattr(sys, self.channel, self.orig_stream)
377 379 printed = self.buffer.getvalue()
378 380 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
379 381 return False
380 382
381 383 @contextmanager
382 384 def mute_warn():
383 385 from IPython.utils import warn
384 386 save_warn = warn.warn
385 387 warn.warn = lambda *a, **kw: None
386 388 try:
387 389 yield
388 390 finally:
389 391 warn.warn = save_warn
390 392
391 393 @contextmanager
392 394 def make_tempfile(name):
393 395 """ Create an empty, named, temporary file for the duration of the context.
394 396 """
395 397 f = open(name, 'w')
396 398 f.close()
397 399 try:
398 400 yield
399 401 finally:
400 402 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now