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