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