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