##// END OF EJS Templates
moved get_ipython_cmd into separate function
Paul Ivanov -
Show More
@@ -1,399 +1,417 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8 from __future__ import absolute_import
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2009-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 22 import pipes
23 23 import re
24 24 import sys
25 25 import tempfile
26 26
27 27 from contextlib import contextmanager
28 28 from io import StringIO
29 29 from subprocess import Popen, PIPE
30 30
31 31 try:
32 32 # These tools are used by parts of the runtime, so we make the nose
33 33 # dependency optional at this point. Nose is a hard dependency to run the
34 34 # test suite, but NOT to use ipython itself.
35 35 import nose.tools as nt
36 36 has_nose = True
37 37 except ImportError:
38 38 has_nose = False
39 39
40 40 from IPython.config.loader import Config
41 41 from IPython.utils.process import getoutputerror
42 42 from IPython.utils.text import list_strings
43 43 from IPython.utils.io import temp_pyfile, Tee
44 44 from IPython.utils import py3compat
45 45 from IPython.utils.encoding import DEFAULT_ENCODING
46 46
47 47 from . import decorators as dec
48 48 from . import skipdoctest
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Functions and classes
52 52 #-----------------------------------------------------------------------------
53 53
54 54 # The docstring for full_path doctests differently on win32 (different path
55 55 # separator) so just skip the doctest there. The example remains informative.
56 56 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
57 57
58 58 @doctest_deco
59 59 def full_path(startPath,files):
60 60 """Make full paths for all the listed files, based on startPath.
61 61
62 62 Only the base part of startPath is kept, since this routine is typically
63 63 used with a script's __file__ variable as startPath. The base of startPath
64 64 is then prepended to all the listed files, forming the output list.
65 65
66 66 Parameters
67 67 ----------
68 68 startPath : string
69 69 Initial path to use as the base for the results. This path is split
70 70 using os.path.split() and only its first component is kept.
71 71
72 72 files : string or list
73 73 One or more files.
74 74
75 75 Examples
76 76 --------
77 77
78 78 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
79 79 ['/foo/a.txt', '/foo/b.txt']
80 80
81 81 >>> full_path('/foo',['a.txt','b.txt'])
82 82 ['/a.txt', '/b.txt']
83 83
84 84 If a single file is given, the output is still a list:
85 85 >>> full_path('/foo','a.txt')
86 86 ['/a.txt']
87 87 """
88 88
89 89 files = list_strings(files)
90 90 base = os.path.split(startPath)[0]
91 91 return [ os.path.join(base,f) for f in files ]
92 92
93 93
94 94 def parse_test_output(txt):
95 95 """Parse the output of a test run and return errors, failures.
96 96
97 97 Parameters
98 98 ----------
99 99 txt : str
100 100 Text output of a test run, assumed to contain a line of one of the
101 101 following forms::
102 102
103 103 'FAILED (errors=1)'
104 104 'FAILED (failures=1)'
105 105 'FAILED (errors=1, failures=1)'
106 106
107 107 Returns
108 108 -------
109 109 nerr, nfail: number of errors and failures.
110 110 """
111 111
112 112 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 113 if err_m:
114 114 nerr = int(err_m.group(1))
115 115 nfail = 0
116 116 return nerr, nfail
117 117
118 118 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 119 if fail_m:
120 120 nerr = 0
121 121 nfail = int(fail_m.group(1))
122 122 return nerr, nfail
123 123
124 124 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 125 re.MULTILINE)
126 126 if both_m:
127 127 nerr = int(both_m.group(1))
128 128 nfail = int(both_m.group(2))
129 129 return nerr, nfail
130 130
131 131 # If the input didn't match any of these forms, assume no error/failures
132 132 return 0, 0
133 133
134 134
135 135 # So nose doesn't think this is a test
136 136 parse_test_output.__test__ = False
137 137
138 138
139 139 def default_argv():
140 140 """Return a valid default argv for creating testing instances of ipython"""
141 141
142 142 return ['--quick', # so no config file is loaded
143 143 # Other defaults to minimize side effects on stdout
144 144 '--colors=NoColor', '--no-term-title','--no-banner',
145 145 '--autocall=0']
146 146
147 147
148 148 def default_config():
149 149 """Return a config object with good defaults for testing."""
150 150 config = Config()
151 151 config.TerminalInteractiveShell.colors = 'NoColor'
152 152 config.TerminalTerminalInteractiveShell.term_title = False,
153 153 config.TerminalInteractiveShell.autocall = 0
154 154 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
155 155 config.HistoryManager.db_cache_size = 10000
156 156 return config
157 157
158 158
159 def get_ipython_cmd(as_string=False):
160 """
161 Return appropriate IPython command line name. By default, this will return
162 a list that can be used with subprocess.Popen, for example, but passing
163 `as_string=True` allows for returning the IPython command as a string.
164
165 Parameters
166 ----------
167 as_string: bool
168 Flag to allow to return the command as a string.
169 """
170 # FIXME: remove workaround for 2.6 support
171 if sys.version_info[:2] > (2,6):
172 ipython_cmd = [sys.executable, "-m", "IPython"]
173 else:
174 ipython_cmd = ["ipython"]
175
176 if as_string:
177 ipython_cmd = " ".join(ipython_cmd)
178
179 return ipython_cmd
180
159 181 def ipexec(fname, options=None):
160 182 """Utility to call 'ipython filename'.
161 183
162 184 Starts IPython with a minimal and safe configuration to make startup as fast
163 185 as possible.
164 186
165 187 Note that this starts IPython in a subprocess!
166 188
167 189 Parameters
168 190 ----------
169 191 fname : str
170 192 Name of file to be executed (should have .py or .ipy extension).
171 193
172 194 options : optional, list
173 195 Extra command-line flags to be passed to IPython.
174 196
175 197 Returns
176 198 -------
177 199 (stdout, stderr) of ipython subprocess.
178 200 """
179 201 if options is None: options = []
180 202
181 203 # For these subprocess calls, eliminate all prompt printing so we only see
182 204 # output from script execution
183 205 prompt_opts = [ '--PromptManager.in_template=""',
184 206 '--PromptManager.in2_template=""',
185 207 '--PromptManager.out_template=""'
186 208 ]
187 209 cmdargs = default_argv() + prompt_opts + options
188 210
189 211 _ip = get_ipython()
190 212 test_dir = os.path.dirname(__file__)
191
192 # FIXME: remove workaround for 2.6 support
193 if sys.version_info[:2] > (2,6):
194 ipython_cmd = [sys.executable, "-m", "IPython"]
195 else:
196 ipython_cmd = ["ipython"]
213
214 ipython_cmd = get_ipython_cmd()
197 215 # Absolute path for filename
198 216 full_fname = os.path.join(test_dir, fname)
199 217 full_cmd = ipython_cmd + cmdargs + [full_fname]
200 218 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
201 219 out, err = p.communicate()
202 220 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
203 221 # `import readline` causes 'ESC[?1034h' to be output sometimes,
204 222 # so strip that out before doing comparisons
205 223 if out:
206 224 out = re.sub(r'\x1b\[[^h]+h', '', out)
207 225 return out, err
208 226
209 227
210 228 def ipexec_validate(fname, expected_out, expected_err='',
211 229 options=None):
212 230 """Utility to call 'ipython filename' and validate output/error.
213 231
214 232 This function raises an AssertionError if the validation fails.
215 233
216 234 Note that this starts IPython in a subprocess!
217 235
218 236 Parameters
219 237 ----------
220 238 fname : str
221 239 Name of the file to be executed (should have .py or .ipy extension).
222 240
223 241 expected_out : str
224 242 Expected stdout of the process.
225 243
226 244 expected_err : optional, str
227 245 Expected stderr of the process.
228 246
229 247 options : optional, list
230 248 Extra command-line flags to be passed to IPython.
231 249
232 250 Returns
233 251 -------
234 252 None
235 253 """
236 254
237 255 import nose.tools as nt
238 256
239 257 out, err = ipexec(fname, options)
240 258 #print 'OUT', out # dbg
241 259 #print 'ERR', err # dbg
242 260 # If there are any errors, we must check those befor stdout, as they may be
243 261 # more informative than simply having an empty stdout.
244 262 if err:
245 263 if expected_err:
246 264 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
247 265 else:
248 266 raise ValueError('Running file %r produced error: %r' %
249 267 (fname, err))
250 268 # If no errors or output on stderr was expected, match stdout
251 269 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
252 270
253 271
254 272 class TempFileMixin(object):
255 273 """Utility class to create temporary Python/IPython files.
256 274
257 275 Meant as a mixin class for test cases."""
258 276
259 277 def mktmp(self, src, ext='.py'):
260 278 """Make a valid python temp file."""
261 279 fname, f = temp_pyfile(src, ext)
262 280 self.tmpfile = f
263 281 self.fname = fname
264 282
265 283 def tearDown(self):
266 284 if hasattr(self, 'tmpfile'):
267 285 # If the tmpfile wasn't made because of skipped tests, like in
268 286 # win32, there's nothing to cleanup.
269 287 self.tmpfile.close()
270 288 try:
271 289 os.unlink(self.fname)
272 290 except:
273 291 # On Windows, even though we close the file, we still can't
274 292 # delete it. I have no clue why
275 293 pass
276 294
277 295 pair_fail_msg = ("Testing {0}\n\n"
278 296 "In:\n"
279 297 " {1!r}\n"
280 298 "Expected:\n"
281 299 " {2!r}\n"
282 300 "Got:\n"
283 301 " {3!r}\n")
284 302 def check_pairs(func, pairs):
285 303 """Utility function for the common case of checking a function with a
286 304 sequence of input/output pairs.
287 305
288 306 Parameters
289 307 ----------
290 308 func : callable
291 309 The function to be tested. Should accept a single argument.
292 310 pairs : iterable
293 311 A list of (input, expected_output) tuples.
294 312
295 313 Returns
296 314 -------
297 315 None. Raises an AssertionError if any output does not match the expected
298 316 value.
299 317 """
300 318 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
301 319 for inp, expected in pairs:
302 320 out = func(inp)
303 321 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
304 322
305 323
306 324 if py3compat.PY3:
307 325 MyStringIO = StringIO
308 326 else:
309 327 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
310 328 # so we need a class that can handle both.
311 329 class MyStringIO(StringIO):
312 330 def write(self, s):
313 331 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
314 332 super(MyStringIO, self).write(s)
315 333
316 334 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
317 335 -------
318 336 {2!s}
319 337 -------
320 338 """
321 339
322 340 class AssertPrints(object):
323 341 """Context manager for testing that code prints certain text.
324 342
325 343 Examples
326 344 --------
327 345 >>> with AssertPrints("abc", suppress=False):
328 346 ... print "abcd"
329 347 ... print "def"
330 348 ...
331 349 abcd
332 350 def
333 351 """
334 352 def __init__(self, s, channel='stdout', suppress=True):
335 353 self.s = s
336 354 self.channel = channel
337 355 self.suppress = suppress
338 356
339 357 def __enter__(self):
340 358 self.orig_stream = getattr(sys, self.channel)
341 359 self.buffer = MyStringIO()
342 360 self.tee = Tee(self.buffer, channel=self.channel)
343 361 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
344 362
345 363 def __exit__(self, etype, value, traceback):
346 364 self.tee.flush()
347 365 setattr(sys, self.channel, self.orig_stream)
348 366 printed = self.buffer.getvalue()
349 367 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
350 368 return False
351 369
352 370 printed_msg = """Found {0!r} in printed output (on {1}):
353 371 -------
354 372 {2!s}
355 373 -------
356 374 """
357 375
358 376 class AssertNotPrints(AssertPrints):
359 377 """Context manager for checking that certain output *isn't* produced.
360 378
361 379 Counterpart of AssertPrints"""
362 380 def __exit__(self, etype, value, traceback):
363 381 self.tee.flush()
364 382 setattr(sys, self.channel, self.orig_stream)
365 383 printed = self.buffer.getvalue()
366 384 assert self.s not in printed, printed_msg.format(self.s, self.channel, printed)
367 385 return False
368 386
369 387 @contextmanager
370 388 def mute_warn():
371 389 from IPython.utils import warn
372 390 save_warn = warn.warn
373 391 warn.warn = lambda *a, **kw: None
374 392 try:
375 393 yield
376 394 finally:
377 395 warn.warn = save_warn
378 396
379 397 @contextmanager
380 398 def make_tempfile(name):
381 399 """ Create an empty, named, temporary file for the duration of the context.
382 400 """
383 401 f = open(name, 'w')
384 402 f.close()
385 403 try:
386 404 yield
387 405 finally:
388 406 os.unlink(name)
389 407
390 408
391 409 @contextmanager
392 410 def monkeypatch(obj, name, attr):
393 411 """
394 412 Context manager to replace attribute named `name` in `obj` with `attr`.
395 413 """
396 414 orig = getattr(obj, name)
397 415 setattr(obj, name, attr)
398 416 yield
399 417 setattr(obj, name, orig)
General Comments 0
You need to be logged in to leave comments. Login now