##// END OF EJS Templates
remove a few more obsolete twisted notes
MinRK -
Show More
@@ -1,523 +1,520 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009-2011 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Stdlib
29 29 import os
30 30 import os.path as path
31 31 import signal
32 32 import sys
33 33 import subprocess
34 34 import tempfile
35 35 import time
36 36 import warnings
37 37
38 38 # Note: monkeypatch!
39 39 # We need to monkeypatch a small problem in nose itself first, before importing
40 40 # it for actual use. This should get into nose upstream, but its release cycle
41 41 # is slow and we need it for our parametric tests to work correctly.
42 42 from IPython.testing import nosepatch
43 43 # Now, proceed to import nose itself
44 44 import nose.plugins.builtin
45 45 from nose.plugins.xunit import Xunit
46 46 from nose import SkipTest
47 47 from nose.core import TestProgram
48 48
49 49 # Our own imports
50 50 from IPython.utils.importstring import import_item
51 51 from IPython.utils.path import get_ipython_module_path
52 52 from IPython.utils.process import find_cmd, pycmd2argv
53 53 from IPython.utils.sysinfo import sys_info
54 54
55 55 from IPython.testing import globalipapp
56 56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 57 from IPython.external.decorators import KnownFailure, knownfailureif
58 58
59 59 pjoin = path.join
60 60
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Globals
64 64 #-----------------------------------------------------------------------------
65 65
66 66
67 67 #-----------------------------------------------------------------------------
68 68 # Warnings control
69 69 #-----------------------------------------------------------------------------
70 70
71 71 # Twisted generates annoying warnings with Python 2.6, as will do other code
72 72 # that imports 'sets' as of today
73 73 warnings.filterwarnings('ignore', 'the sets module is deprecated',
74 74 DeprecationWarning )
75 75
76 76 # This one also comes from Twisted
77 77 warnings.filterwarnings('ignore', 'the sha module is deprecated',
78 78 DeprecationWarning)
79 79
80 80 # Wx on Fedora11 spits these out
81 81 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
82 82 UserWarning)
83 83
84 84 # ------------------------------------------------------------------------------
85 85 # Monkeypatch Xunit to count known failures as skipped.
86 86 # ------------------------------------------------------------------------------
87 87 def monkeypatch_xunit():
88 88 try:
89 89 knownfailureif(True)(lambda: None)()
90 90 except Exception as e:
91 91 KnownFailureTest = type(e)
92 92
93 93 def addError(self, test, err, capt=None):
94 94 if issubclass(err[0], KnownFailureTest):
95 95 err = (SkipTest,) + err[1:]
96 96 return self.orig_addError(test, err, capt)
97 97
98 98 Xunit.orig_addError = Xunit.addError
99 99 Xunit.addError = addError
100 100
101 101 #-----------------------------------------------------------------------------
102 102 # Logic for skipping doctests
103 103 #-----------------------------------------------------------------------------
104 104 def extract_version(mod):
105 105 return mod.__version__
106 106
107 107 def test_for(item, min_version=None, callback=extract_version):
108 108 """Test to see if item is importable, and optionally check against a minimum
109 109 version.
110 110
111 111 If min_version is given, the default behavior is to check against the
112 112 `__version__` attribute of the item, but specifying `callback` allows you to
113 113 extract the value you are interested in. e.g::
114 114
115 115 In [1]: import sys
116 116
117 117 In [2]: from IPython.testing.iptest import test_for
118 118
119 119 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
120 120 Out[3]: True
121 121
122 122 """
123 123 try:
124 124 check = import_item(item)
125 125 except (ImportError, RuntimeError):
126 126 # GTK reports Runtime error if it can't be initialized even if it's
127 127 # importable.
128 128 return False
129 129 else:
130 130 if min_version:
131 131 if callback:
132 132 # extra processing step to get version to compare
133 133 check = callback(check)
134 134
135 135 return check >= min_version
136 136 else:
137 137 return True
138 138
139 139 # Global dict where we can store information on what we have and what we don't
140 140 # have available at test run time
141 141 have = {}
142 142
143 143 have['curses'] = test_for('_curses')
144 144 have['matplotlib'] = test_for('matplotlib')
145 145 have['pexpect'] = test_for('IPython.external.pexpect')
146 146 have['pymongo'] = test_for('pymongo')
147 147 have['wx'] = test_for('wx')
148 148 have['wx.aui'] = test_for('wx.aui')
149 149 have['qt'] = test_for('IPython.external.qt')
150 150 have['sqlite3'] = test_for('sqlite3')
151 151
152 152 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
153 153
154 154 if os.name == 'nt':
155 155 min_zmq = (2,1,7)
156 156 else:
157 157 min_zmq = (2,1,4)
158 158
159 159 def version_tuple(mod):
160 160 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
161 161 # turn 'dev' into 999, because Python3 rejects str-int comparisons
162 162 vs = mod.__version__.replace('dev', '.999')
163 163 tup = tuple([int(v) for v in vs.split('.') ])
164 164 return tup
165 165
166 166 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
167 167
168 168 #-----------------------------------------------------------------------------
169 169 # Functions and classes
170 170 #-----------------------------------------------------------------------------
171 171
172 172 def report():
173 173 """Return a string with a summary report of test-related variables."""
174 174
175 175 out = [ sys_info(), '\n']
176 176
177 177 avail = []
178 178 not_avail = []
179 179
180 180 for k, is_avail in have.items():
181 181 if is_avail:
182 182 avail.append(k)
183 183 else:
184 184 not_avail.append(k)
185 185
186 186 if avail:
187 187 out.append('\nTools and libraries available at test time:\n')
188 188 avail.sort()
189 189 out.append(' ' + ' '.join(avail)+'\n')
190 190
191 191 if not_avail:
192 192 out.append('\nTools and libraries NOT available at test time:\n')
193 193 not_avail.sort()
194 194 out.append(' ' + ' '.join(not_avail)+'\n')
195 195
196 196 return ''.join(out)
197 197
198 198
199 199 def make_exclude():
200 200 """Make patterns of modules and packages to exclude from testing.
201 201
202 202 For the IPythonDoctest plugin, we need to exclude certain patterns that
203 203 cause testing problems. We should strive to minimize the number of
204 204 skipped modules, since this means untested code.
205 205
206 206 These modules and packages will NOT get scanned by nose at all for tests.
207 207 """
208 208 # Simple utility to make IPython paths more readably, we need a lot of
209 209 # these below
210 210 ipjoin = lambda *paths: pjoin('IPython', *paths)
211 211
212 212 exclusions = [ipjoin('external'),
213 213 pjoin('IPython_doctest_plugin'),
214 214 ipjoin('quarantine'),
215 215 ipjoin('deathrow'),
216 216 ipjoin('testing', 'attic'),
217 217 # This guy is probably attic material
218 218 ipjoin('testing', 'mkdoctests'),
219 219 # Testing inputhook will need a lot of thought, to figure out
220 220 # how to have tests that don't lock up with the gui event
221 221 # loops in the picture
222 222 ipjoin('lib', 'inputhook'),
223 223 # Config files aren't really importable stand-alone
224 224 ipjoin('config', 'default'),
225 225 ipjoin('config', 'profile'),
226 226 ]
227 227 if not have['sqlite3']:
228 228 exclusions.append(ipjoin('core', 'tests', 'test_history'))
229 229 exclusions.append(ipjoin('core', 'history'))
230 230 if not have['wx']:
231 231 exclusions.append(ipjoin('lib', 'inputhookwx'))
232 232
233 233 # We do this unconditionally, so that the test suite doesn't import
234 234 # gtk, changing the default encoding and masking some unicode bugs.
235 235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
236 236
237 237 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 238 # See ticket https://github.com/ipython/ipython/issues/87
239 239 if sys.platform == 'win32':
240 240 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
241 241 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
242 242
243 243 if not have['pexpect']:
244 244 exclusions.extend([ipjoin('scripts', 'irunner'),
245 245 ipjoin('lib', 'irunner'),
246 246 ipjoin('lib', 'tests', 'test_irunner'),
247 247 ipjoin('frontend', 'terminal', 'console'),
248 248 ])
249 249
250 250 if not have['zmq']:
251 251 exclusions.append(ipjoin('zmq'))
252 252 exclusions.append(ipjoin('frontend', 'qt'))
253 253 exclusions.append(ipjoin('frontend', 'html'))
254 254 exclusions.append(ipjoin('frontend', 'consoleapp.py'))
255 255 exclusions.append(ipjoin('frontend', 'terminal', 'console'))
256 256 exclusions.append(ipjoin('parallel'))
257 257 elif not have['qt']:
258 258 exclusions.append(ipjoin('frontend', 'qt'))
259 259
260 260 if not have['pymongo']:
261 261 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
262 262 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
263 263
264 264 if not have['matplotlib']:
265 265 exclusions.extend([ipjoin('core', 'pylabtools'),
266 266 ipjoin('core', 'tests', 'test_pylabtools')])
267 267
268 268 if not have['tornado']:
269 269 exclusions.append(ipjoin('frontend', 'html'))
270 270
271 271 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
272 272 if sys.platform == 'win32':
273 273 exclusions = [s.replace('\\','\\\\') for s in exclusions]
274 274
275 275 return exclusions
276 276
277 277
278 278 class IPTester(object):
279 279 """Call that calls iptest or trial in a subprocess.
280 280 """
281 281 #: string, name of test runner that will be called
282 282 runner = None
283 283 #: list, parameters for test runner
284 284 params = None
285 285 #: list, arguments of system call to be made to call test runner
286 286 call_args = None
287 287 #: list, process ids of subprocesses we start (for cleanup)
288 288 pids = None
289 289 #: str, coverage xml output file
290 290 coverage_xml = None
291 291
292 292 def __init__(self, runner='iptest', params=None):
293 293 """Create new test runner."""
294 294 p = os.path
295 295 if runner == 'iptest':
296 296 iptest_app = get_ipython_module_path('IPython.testing.iptest')
297 297 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
298 298 else:
299 299 raise Exception('Not a valid test runner: %s' % repr(runner))
300 300 if params is None:
301 301 params = []
302 302 if isinstance(params, str):
303 303 params = [params]
304 304 self.params = params
305 305
306 306 # Assemble call
307 307 self.call_args = self.runner+self.params
308 308
309 309 # Find the section we're testing (IPython.foo)
310 310 for sect in self.params:
311 311 if sect.startswith('IPython'): break
312 312 else:
313 313 raise ValueError("Section not found", self.params)
314 314
315 315 if '--with-xunit' in self.call_args:
316 316 self.call_args.append('--xunit-file=%s' % path.abspath(sect+'.xunit.xml'))
317 317
318 318 if '--with-xml-coverage' in self.call_args:
319 319 self.coverage_xml = path.abspath(sect+".coverage.xml")
320 320 self.call_args.remove('--with-xml-coverage')
321 321 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
322 322
323 323 # Store pids of anything we start to clean up on deletion, if possible
324 324 # (on posix only, since win32 has no os.kill)
325 325 self.pids = []
326 326
327 327 if sys.platform == 'win32':
328 328 def _run_cmd(self):
329 329 # On Windows, use os.system instead of subprocess.call, because I
330 330 # was having problems with subprocess and I just don't know enough
331 331 # about win32 to debug this reliably. Os.system may be the 'old
332 332 # fashioned' way to do it, but it works just fine. If someone
333 333 # later can clean this up that's fine, as long as the tests run
334 334 # reliably in win32.
335 335 # What types of problems are you having. They may be related to
336 336 # running Python in unboffered mode. BG.
337 337 return os.system(' '.join(self.call_args))
338 338 else:
339 339 def _run_cmd(self):
340 340 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
341 341 subp = subprocess.Popen(self.call_args)
342 342 self.pids.append(subp.pid)
343 343 # If this fails, the pid will be left in self.pids and cleaned up
344 344 # later, but if the wait call succeeds, then we can clear the
345 345 # stored pid.
346 346 retcode = subp.wait()
347 347 self.pids.pop()
348 348 return retcode
349 349
350 350 def run(self):
351 351 """Run the stored commands"""
352 352 try:
353 353 retcode = self._run_cmd()
354 354 except:
355 355 import traceback
356 356 traceback.print_exc()
357 357 return 1 # signal failure
358 358
359 359 if self.coverage_xml:
360 360 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
361 361 return retcode
362 362
363 363 def __del__(self):
364 364 """Cleanup on exit by killing any leftover processes."""
365 365
366 366 if not hasattr(os, 'kill'):
367 367 return
368 368
369 369 for pid in self.pids:
370 370 try:
371 371 print 'Cleaning stale PID:', pid
372 372 os.kill(pid, signal.SIGKILL)
373 373 except OSError:
374 374 # This is just a best effort, if we fail or the process was
375 375 # really gone, ignore it.
376 376 pass
377 377
378 378
379 379 def make_runners():
380 380 """Define the top-level packages that need to be tested.
381 381 """
382 382
383 383 # Packages to be tested via nose, that only depend on the stdlib
384 384 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
385 385 'scripts', 'testing', 'utils', 'nbformat' ]
386 386
387 387 if have['zmq']:
388 388 nose_pkg_names.append('parallel')
389 389
390 390 # For debugging this code, only load quick stuff
391 391 #nose_pkg_names = ['core', 'extensions'] # dbg
392 392
393 393 # Make fully qualified package names prepending 'IPython.' to our name lists
394 394 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
395 395
396 396 # Make runners
397 397 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
398 398
399 399 return runners
400 400
401 401
402 402 def run_iptest():
403 403 """Run the IPython test suite using nose.
404 404
405 405 This function is called when this script is **not** called with the form
406 406 `iptest all`. It simply calls nose with appropriate command line flags
407 407 and accepts all of the standard nose arguments.
408 408 """
409 409 # Apply our monkeypatch to Xunit
410 410 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
411 411 monkeypatch_xunit()
412 412
413 413 warnings.filterwarnings('ignore',
414 414 'This will be removed soon. Use IPython.testing.util instead')
415 415
416 416 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
417 417
418 # Loading ipdoctest causes problems with Twisted, but
419 # our test suite runner now separates things and runs
420 # all Twisted tests with trial.
421 418 '--with-ipdoctest',
422 419 '--ipdoctest-tests','--ipdoctest-extension=txt',
423 420
424 421 # We add --exe because of setuptools' imbecility (it
425 422 # blindly does chmod +x on ALL files). Nose does the
426 423 # right thing and it tries to avoid executables,
427 424 # setuptools unfortunately forces our hand here. This
428 425 # has been discussed on the distutils list and the
429 426 # setuptools devs refuse to fix this problem!
430 427 '--exe',
431 428 ]
432 429
433 430 if nose.__version__ >= '0.11':
434 431 # I don't fully understand why we need this one, but depending on what
435 432 # directory the test suite is run from, if we don't give it, 0 tests
436 433 # get run. Specifically, if the test suite is run from the source dir
437 434 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
438 435 # even if the same call done in this directory works fine). It appears
439 436 # that if the requested package is in the current dir, nose bails early
440 437 # by default. Since it's otherwise harmless, leave it in by default
441 438 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
442 439 argv.append('--traverse-namespace')
443 440
444 441 # use our plugin for doctesting. It will remove the standard doctest plugin
445 442 # if it finds it enabled
446 443 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
447 444 # We need a global ipython running in this process
448 445 globalipapp.start_ipython()
449 446 # Now nose can run
450 447 TestProgram(argv=argv, addplugins=plugins)
451 448
452 449
453 450 def run_iptestall():
454 451 """Run the entire IPython test suite by calling nose and trial.
455 452
456 453 This function constructs :class:`IPTester` instances for all IPython
457 454 modules and package and then runs each of them. This causes the modules
458 455 and packages of IPython to be tested each in their own subprocess using
459 nose or twisted.trial appropriately.
456 nose.
460 457 """
461 458
462 459 runners = make_runners()
463 460
464 461 # Run the test runners in a temporary dir so we can nuke it when finished
465 462 # to clean up any junk files left over by accident. This also makes it
466 463 # robust against being run in non-writeable directories by mistake, as the
467 464 # temp dir will always be user-writeable.
468 465 curdir = os.getcwdu()
469 466 testdir = tempfile.gettempdir()
470 467 os.chdir(testdir)
471 468
472 469 # Run all test runners, tracking execution time
473 470 failed = []
474 471 t_start = time.time()
475 472 try:
476 473 for (name, runner) in runners:
477 474 print '*'*70
478 475 print 'IPython test group:',name
479 476 res = runner.run()
480 477 if res:
481 478 failed.append( (name, runner) )
482 479 finally:
483 480 os.chdir(curdir)
484 481 t_end = time.time()
485 482 t_tests = t_end - t_start
486 483 nrunners = len(runners)
487 484 nfail = len(failed)
488 485 # summarize results
489 486 print
490 487 print '*'*70
491 488 print 'Test suite completed for system with the following information:'
492 489 print report()
493 490 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
494 491 print
495 492 print 'Status:'
496 493 if not failed:
497 494 print 'OK'
498 495 else:
499 496 # If anything went wrong, point out what command to rerun manually to
500 497 # see the actual errors and individual summary
501 498 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
502 499 for name, failed_runner in failed:
503 500 print '-'*40
504 501 print 'Runner failed:',name
505 502 print 'You may wish to rerun this one individually, with:'
506 503 print ' '.join(failed_runner.call_args)
507 504 print
508 505 # Ensure that our exit code indicates failure
509 506 sys.exit(1)
510 507
511 508
512 509 def main():
513 510 for arg in sys.argv[1:]:
514 511 if arg.startswith('IPython'):
515 512 # This is in-process
516 513 run_iptest()
517 514 else:
518 515 # This starts subprocesses
519 516 run_iptestall()
520 517
521 518
522 519 if __name__ == '__main__':
523 520 main()
@@ -1,396 +1,391 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools.
2 2
3 3 In particular, this module exposes a set of top-level assert* functions that
4 4 can be used in place of nose.tools.assert* in method generators (the ones in
5 5 nose can not, at least as of nose 0.10.4).
6 6
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
11
12 7
13 8 Authors
14 9 -------
15 10 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 11 """
17 12
18 13 from __future__ import absolute_import
19 14
20 15 #-----------------------------------------------------------------------------
21 16 # Copyright (C) 2009-2011 The IPython Development Team
22 17 #
23 18 # Distributed under the terms of the BSD License. The full license is in
24 19 # the file COPYING, distributed as part of this software.
25 20 #-----------------------------------------------------------------------------
26 21
27 22 #-----------------------------------------------------------------------------
28 23 # Imports
29 24 #-----------------------------------------------------------------------------
30 25
31 26 import os
32 27 import re
33 28 import sys
34 29 import tempfile
35 30
36 31 from contextlib import contextmanager
37 32 from io import StringIO
38 33
39 34 try:
40 35 # These tools are used by parts of the runtime, so we make the nose
41 36 # dependency optional at this point. Nose is a hard dependency to run the
42 37 # test suite, but NOT to use ipython itself.
43 38 import nose.tools as nt
44 39 has_nose = True
45 40 except ImportError:
46 41 has_nose = False
47 42
48 43 from IPython.config.loader import Config
49 44 from IPython.utils.process import find_cmd, getoutputerror
50 45 from IPython.utils.text import list_strings, getdefaultencoding
51 46 from IPython.utils.io import temp_pyfile, Tee
52 47 from IPython.utils import py3compat
53 48
54 49 from . import decorators as dec
55 50 from . import skipdoctest
56 51
57 52 #-----------------------------------------------------------------------------
58 53 # Globals
59 54 #-----------------------------------------------------------------------------
60 55
61 56 # Make a bunch of nose.tools assert wrappers that can be used in test
62 57 # generators. This will expose an assert* function for each one in nose.tools.
63 58
64 59 _tpl = """
65 60 def %(name)s(*a,**kw):
66 61 return nt.%(name)s(*a,**kw)
67 62 """
68 63
69 64 if has_nose:
70 65 for _x in [a for a in dir(nt) if a.startswith('assert')]:
71 66 exec _tpl % dict(name=_x)
72 67
73 68 #-----------------------------------------------------------------------------
74 69 # Functions and classes
75 70 #-----------------------------------------------------------------------------
76 71
77 72 # The docstring for full_path doctests differently on win32 (different path
78 73 # separator) so just skip the doctest there. The example remains informative.
79 74 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
80 75
81 76 @doctest_deco
82 77 def full_path(startPath,files):
83 78 """Make full paths for all the listed files, based on startPath.
84 79
85 80 Only the base part of startPath is kept, since this routine is typically
86 81 used with a script's __file__ variable as startPath. The base of startPath
87 82 is then prepended to all the listed files, forming the output list.
88 83
89 84 Parameters
90 85 ----------
91 86 startPath : string
92 87 Initial path to use as the base for the results. This path is split
93 88 using os.path.split() and only its first component is kept.
94 89
95 90 files : string or list
96 91 One or more files.
97 92
98 93 Examples
99 94 --------
100 95
101 96 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
102 97 ['/foo/a.txt', '/foo/b.txt']
103 98
104 99 >>> full_path('/foo',['a.txt','b.txt'])
105 100 ['/a.txt', '/b.txt']
106 101
107 102 If a single file is given, the output is still a list:
108 103 >>> full_path('/foo','a.txt')
109 104 ['/a.txt']
110 105 """
111 106
112 107 files = list_strings(files)
113 108 base = os.path.split(startPath)[0]
114 109 return [ os.path.join(base,f) for f in files ]
115 110
116 111
117 112 def parse_test_output(txt):
118 113 """Parse the output of a test run and return errors, failures.
119 114
120 115 Parameters
121 116 ----------
122 117 txt : str
123 118 Text output of a test run, assumed to contain a line of one of the
124 119 following forms::
125 120 'FAILED (errors=1)'
126 121 'FAILED (failures=1)'
127 122 'FAILED (errors=1, failures=1)'
128 123
129 124 Returns
130 125 -------
131 126 nerr, nfail: number of errors and failures.
132 127 """
133 128
134 129 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
135 130 if err_m:
136 131 nerr = int(err_m.group(1))
137 132 nfail = 0
138 133 return nerr, nfail
139 134
140 135 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
141 136 if fail_m:
142 137 nerr = 0
143 138 nfail = int(fail_m.group(1))
144 139 return nerr, nfail
145 140
146 141 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
147 142 re.MULTILINE)
148 143 if both_m:
149 144 nerr = int(both_m.group(1))
150 145 nfail = int(both_m.group(2))
151 146 return nerr, nfail
152 147
153 148 # If the input didn't match any of these forms, assume no error/failures
154 149 return 0, 0
155 150
156 151
157 152 # So nose doesn't think this is a test
158 153 parse_test_output.__test__ = False
159 154
160 155
161 156 def default_argv():
162 157 """Return a valid default argv for creating testing instances of ipython"""
163 158
164 159 return ['--quick', # so no config file is loaded
165 160 # Other defaults to minimize side effects on stdout
166 161 '--colors=NoColor', '--no-term-title','--no-banner',
167 162 '--autocall=0']
168 163
169 164
170 165 def default_config():
171 166 """Return a config object with good defaults for testing."""
172 167 config = Config()
173 168 config.TerminalInteractiveShell.colors = 'NoColor'
174 169 config.TerminalTerminalInteractiveShell.term_title = False,
175 170 config.TerminalInteractiveShell.autocall = 0
176 171 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
177 172 config.HistoryManager.db_cache_size = 10000
178 173 return config
179 174
180 175
181 176 def ipexec(fname, options=None):
182 177 """Utility to call 'ipython filename'.
183 178
184 179 Starts IPython witha minimal and safe configuration to make startup as fast
185 180 as possible.
186 181
187 182 Note that this starts IPython in a subprocess!
188 183
189 184 Parameters
190 185 ----------
191 186 fname : str
192 187 Name of file to be executed (should have .py or .ipy extension).
193 188
194 189 options : optional, list
195 190 Extra command-line flags to be passed to IPython.
196 191
197 192 Returns
198 193 -------
199 194 (stdout, stderr) of ipython subprocess.
200 195 """
201 196 if options is None: options = []
202 197
203 198 # For these subprocess calls, eliminate all prompt printing so we only see
204 199 # output from script execution
205 200 prompt_opts = [ '--PromptManager.in_template=""',
206 201 '--PromptManager.in2_template=""',
207 202 '--PromptManager.out_template=""'
208 203 ]
209 204 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210 205
211 206 _ip = get_ipython()
212 207 test_dir = os.path.dirname(__file__)
213 208
214 209 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
215 210 # Absolute path for filename
216 211 full_fname = os.path.join(test_dir, fname)
217 212 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
218 213 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
219 214 out, err = getoutputerror(full_cmd)
220 215 # `import readline` causes 'ESC[?1034h' to be output sometimes,
221 216 # so strip that out before doing comparisons
222 217 if out:
223 218 out = re.sub(r'\x1b\[[^h]+h', '', out)
224 219 return out, err
225 220
226 221
227 222 def ipexec_validate(fname, expected_out, expected_err='',
228 223 options=None):
229 224 """Utility to call 'ipython filename' and validate output/error.
230 225
231 226 This function raises an AssertionError if the validation fails.
232 227
233 228 Note that this starts IPython in a subprocess!
234 229
235 230 Parameters
236 231 ----------
237 232 fname : str
238 233 Name of the file to be executed (should have .py or .ipy extension).
239 234
240 235 expected_out : str
241 236 Expected stdout of the process.
242 237
243 238 expected_err : optional, str
244 239 Expected stderr of the process.
245 240
246 241 options : optional, list
247 242 Extra command-line flags to be passed to IPython.
248 243
249 244 Returns
250 245 -------
251 246 None
252 247 """
253 248
254 249 import nose.tools as nt
255 250
256 251 out, err = ipexec(fname, options)
257 252 #print 'OUT', out # dbg
258 253 #print 'ERR', err # dbg
259 254 # If there are any errors, we must check those befor stdout, as they may be
260 255 # more informative than simply having an empty stdout.
261 256 if err:
262 257 if expected_err:
263 258 nt.assert_equals(err.strip(), expected_err.strip())
264 259 else:
265 260 raise ValueError('Running file %r produced error: %r' %
266 261 (fname, err))
267 262 # If no errors or output on stderr was expected, match stdout
268 263 nt.assert_equals(out.strip(), expected_out.strip())
269 264
270 265
271 266 class TempFileMixin(object):
272 267 """Utility class to create temporary Python/IPython files.
273 268
274 269 Meant as a mixin class for test cases."""
275 270
276 271 def mktmp(self, src, ext='.py'):
277 272 """Make a valid python temp file."""
278 273 fname, f = temp_pyfile(src, ext)
279 274 self.tmpfile = f
280 275 self.fname = fname
281 276
282 277 def tearDown(self):
283 278 if hasattr(self, 'tmpfile'):
284 279 # If the tmpfile wasn't made because of skipped tests, like in
285 280 # win32, there's nothing to cleanup.
286 281 self.tmpfile.close()
287 282 try:
288 283 os.unlink(self.fname)
289 284 except:
290 285 # On Windows, even though we close the file, we still can't
291 286 # delete it. I have no clue why
292 287 pass
293 288
294 289 pair_fail_msg = ("Testing {0}\n\n"
295 290 "In:\n"
296 291 " {1!r}\n"
297 292 "Expected:\n"
298 293 " {2!r}\n"
299 294 "Got:\n"
300 295 " {3!r}\n")
301 296 def check_pairs(func, pairs):
302 297 """Utility function for the common case of checking a function with a
303 298 sequence of input/output pairs.
304 299
305 300 Parameters
306 301 ----------
307 302 func : callable
308 303 The function to be tested. Should accept a single argument.
309 304 pairs : iterable
310 305 A list of (input, expected_output) tuples.
311 306
312 307 Returns
313 308 -------
314 309 None. Raises an AssertionError if any output does not match the expected
315 310 value.
316 311 """
317 312 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
318 313 for inp, expected in pairs:
319 314 out = func(inp)
320 315 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
321 316
322 317
323 318 if py3compat.PY3:
324 319 MyStringIO = StringIO
325 320 else:
326 321 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
327 322 # so we need a class that can handle both.
328 323 class MyStringIO(StringIO):
329 324 def write(self, s):
330 325 s = py3compat.cast_unicode(s, encoding=getdefaultencoding())
331 326 super(MyStringIO, self).write(s)
332 327
333 328 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
334 329 {2!r}"""
335 330
336 331 class AssertPrints(object):
337 332 """Context manager for testing that code prints certain text.
338 333
339 334 Examples
340 335 --------
341 336 >>> with AssertPrints("abc", suppress=False):
342 337 ... print "abcd"
343 338 ... print "def"
344 339 ...
345 340 abcd
346 341 def
347 342 """
348 343 def __init__(self, s, channel='stdout', suppress=True):
349 344 self.s = s
350 345 self.channel = channel
351 346 self.suppress = suppress
352 347
353 348 def __enter__(self):
354 349 self.orig_stream = getattr(sys, self.channel)
355 350 self.buffer = MyStringIO()
356 351 self.tee = Tee(self.buffer, channel=self.channel)
357 352 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
358 353
359 354 def __exit__(self, etype, value, traceback):
360 355 self.tee.flush()
361 356 setattr(sys, self.channel, self.orig_stream)
362 357 printed = self.buffer.getvalue()
363 358 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
364 359 return False
365 360
366 361 class AssertNotPrints(AssertPrints):
367 362 """Context manager for checking that certain output *isn't* produced.
368 363
369 364 Counterpart of AssertPrints"""
370 365 def __exit__(self, etype, value, traceback):
371 366 self.tee.flush()
372 367 setattr(sys, self.channel, self.orig_stream)
373 368 printed = self.buffer.getvalue()
374 369 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
375 370 return False
376 371
377 372 @contextmanager
378 373 def mute_warn():
379 374 from IPython.utils import warn
380 375 save_warn = warn.warn
381 376 warn.warn = lambda *a, **kw: None
382 377 try:
383 378 yield
384 379 finally:
385 380 warn.warn = save_warn
386 381
387 382 @contextmanager
388 383 def make_tempfile(name):
389 384 """ Create an empty, named, temporary file for the duration of the context.
390 385 """
391 386 f = open(name, 'w')
392 387 f.close()
393 388 try:
394 389 yield
395 390 finally:
396 391 os.unlink(name)
General Comments 0
You need to be logged in to leave comments. Login now