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