##// END OF EJS Templates
Robustness fixes in test suite machinery....
Fernando Perez -
Show More
@@ -1,30 +1,31 b''
1 1 """Simple script to be run *twice*, to check reference counting bugs.
2 2
3 3 See test_run for details."""
4 4
5 5 import sys
6 6
7 7 # We want to ensure that while objects remain available for immediate access,
8 8 # objects from *previous* runs of the same script get collected, to avoid
9 9 # accumulating massive amounts of old references.
10 10 class C(object):
11 11 def __init__(self,name):
12 12 self.name = name
13 13
14 14 def __del__(self):
15 15 print 'tclass.py: deleting object:',self.name
16
16 sys.stdout.flush()
17 17
18 18 try:
19 19 name = sys.argv[1]
20 20 except IndexError:
21 21 pass
22 22 else:
23 23 if name.startswith('C'):
24 24 c = C(name)
25 25
26 26 #print >> sys.stderr, "ARGV:", sys.argv # dbg
27 27
28 28 # This next print statement is NOT debugging, we're making the check on a
29 29 # completely separate process so we verify by capturing stdout:
30 30 print 'ARGV 1-:', sys.argv[1:]
31 sys.stdout.flush()
@@ -1,434 +1,450 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) or trial 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 For now, this script requires that both nose and twisted are installed. This
16 16 will change in the future.
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Module imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # Stdlib
24 24 import os
25 25 import os.path as path
26 26 import signal
27 27 import sys
28 28 import subprocess
29 29 import tempfile
30 30 import time
31 31 import warnings
32 32
33 33
34 34 # Ugly, but necessary hack to ensure the test suite finds our version of
35 35 # IPython and not a possibly different one that may exist system-wide.
36 36 # Note that this must be done here, so the imports that come next work
37 37 # correctly even if IPython isn't installed yet.
38 38 p = os.path
39 39 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
40 40 sys.path.insert(0, ippath)
41 41
42 42 # Note: monkeypatch!
43 43 # We need to monkeypatch a small problem in nose itself first, before importing
44 44 # it for actual use. This should get into nose upstream, but its release cycle
45 45 # is slow and we need it for our parametric tests to work correctly.
46 46 from IPython.testing import nosepatch
47 47 # Now, proceed to import nose itself
48 48 import nose.plugins.builtin
49 49 from nose.core import TestProgram
50 50
51 51 # Our own imports
52 52 from IPython.utils import genutils
53 53 from IPython.utils.platutils import find_cmd, FindCmdError
54 54 from IPython.testing import globalipapp
55 55 from IPython.testing import tools
56 56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
57 57
58 58 pjoin = path.join
59 59
60
61 #-----------------------------------------------------------------------------
62 # Globals
63 #-----------------------------------------------------------------------------
64
65 # By default, we assume IPython has been installed. But if the test suite is
66 # being run from a source tree that has NOT been installed yet, this flag can
67 # be set to False by the entry point scripts, to let us know that we must call
68 # the source tree versions of the scripts which manipulate sys.path instead of
69 # assuming that things exist system-wide.
70 INSTALLED = True
71
60 72 #-----------------------------------------------------------------------------
61 73 # Warnings control
62 74 #-----------------------------------------------------------------------------
63 75 # Twisted generates annoying warnings with Python 2.6, as will do other code
64 76 # that imports 'sets' as of today
65 77 warnings.filterwarnings('ignore', 'the sets module is deprecated',
66 78 DeprecationWarning )
67 79
68 80 # This one also comes from Twisted
69 81 warnings.filterwarnings('ignore', 'the sha module is deprecated',
70 82 DeprecationWarning)
71 83
72 84 # Wx on Fedora11 spits these out
73 85 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
74 86 UserWarning)
75 87
76 88 #-----------------------------------------------------------------------------
77 89 # Logic for skipping doctests
78 90 #-----------------------------------------------------------------------------
79 91
80 92 def test_for(mod):
81 93 """Test to see if mod is importable."""
82 94 try:
83 95 __import__(mod)
84 96 except (ImportError, RuntimeError):
85 97 # GTK reports Runtime error if it can't be initialized even if it's
86 98 # importable.
87 99 return False
88 100 else:
89 101 return True
90 102
91
92 103 have_curses = test_for('_curses')
93 104 have_wx = test_for('wx')
94 105 have_wx_aui = test_for('wx.aui')
95 106 have_zi = test_for('zope.interface')
96 107 have_twisted = test_for('twisted')
97 108 have_foolscap = test_for('foolscap')
98 109 have_objc = test_for('objc')
99 110 have_pexpect = test_for('pexpect')
100 111 have_gtk = test_for('gtk')
101 112 have_gobject = test_for('gobject')
102 113
114 #-----------------------------------------------------------------------------
115 # Functions and classes
116 #-----------------------------------------------------------------------------
103 117
104 118 def make_exclude():
105 119 """Make patterns of modules and packages to exclude from testing.
106 120
107 121 For the IPythonDoctest plugin, we need to exclude certain patterns that
108 122 cause testing problems. We should strive to minimize the number of
109 123 skipped modules, since this means untested code. As the testing
110 124 machinery solidifies, this list should eventually become empty.
111 125 These modules and packages will NOT get scanned by nose at all for tests.
112 126 """
113 127 # Simple utility to make IPython paths more readably, we need a lot of
114 128 # these below
115 129 ipjoin = lambda *paths: pjoin('IPython', *paths)
116 130
117 131 exclusions = [ipjoin('external'),
118 132 ipjoin('frontend', 'process', 'winprocess.py'),
119 133 # Deprecated old Shell and iplib modules, skip to avoid
120 134 # warnings
121 135 ipjoin('Shell'),
122 136 ipjoin('iplib'),
123 137 pjoin('IPython_doctest_plugin'),
124 138 ipjoin('quarantine'),
125 139 ipjoin('deathrow'),
126 140 ipjoin('testing', 'attic'),
127 141 # This guy is probably attic material
128 142 ipjoin('testing', 'mkdoctests'),
129 143 # Testing inputhook will need a lot of thought, to figure out
130 144 # how to have tests that don't lock up with the gui event
131 145 # loops in the picture
132 146 ipjoin('lib', 'inputhook'),
133 147 # Config files aren't really importable stand-alone
134 148 ipjoin('config', 'default'),
135 149 ipjoin('config', 'profile'),
136 150 ]
137 151
138 152 if not have_wx:
139 153 exclusions.append(ipjoin('gui'))
140 154 exclusions.append(ipjoin('frontend', 'wx'))
141 155 exclusions.append(ipjoin('lib', 'inputhookwx'))
142 156
143 157 if not have_gtk or not have_gobject:
144 158 exclusions.append(ipjoin('lib', 'inputhookgtk'))
145 159
146 160 if not have_wx_aui:
147 161 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
148 162
149 163 if not have_objc:
150 164 exclusions.append(ipjoin('frontend', 'cocoa'))
151 165
152 166 if not sys.platform == 'win32':
153 167 exclusions.append(ipjoin('utils', 'platutils_win32'))
154 168
155 169 # These have to be skipped on win32 because the use echo, rm, cd, etc.
156 170 # See ticket https://bugs.launchpad.net/bugs/366982
157 171 if sys.platform == 'win32':
158 172 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
159 173 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
160 174
161 175 if not os.name == 'posix':
162 176 exclusions.append(ipjoin('utils', 'platutils_posix'))
163 177
164 178 if not have_pexpect:
165 179 exclusions.extend([ipjoin('scripts', 'irunner'),
166 180 ipjoin('lib', 'irunner')])
167 181
168 182 # This is scary. We still have things in frontend and testing that
169 183 # are being tested by nose that use twisted. We need to rethink
170 184 # how we are isolating dependencies in testing.
171 185 if not (have_twisted and have_zi and have_foolscap):
172 186 exclusions.extend(
173 187 [ipjoin('frontend', 'asyncfrontendbase'),
174 188 ipjoin('frontend', 'prefilterfrontend'),
175 189 ipjoin('frontend', 'frontendbase'),
176 190 ipjoin('frontend', 'linefrontendbase'),
177 191 ipjoin('frontend', 'tests', 'test_linefrontend'),
178 192 ipjoin('frontend', 'tests', 'test_frontendbase'),
179 193 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
180 194 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
181 195 ipjoin('testing', 'parametric'),
182 196 ipjoin('testing', 'util'),
183 197 ipjoin('testing', 'tests', 'test_decorators_trial'),
184 198 ] )
185 199
186 200 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
187 201 if sys.platform == 'win32':
188 202 exclusions = [s.replace('\\','\\\\') for s in exclusions]
189 203
190 204 return exclusions
191 205
192 206
193 #-----------------------------------------------------------------------------
194 # Functions and classes
195 #-----------------------------------------------------------------------------
196
197 207 class IPTester(object):
198 208 """Call that calls iptest or trial in a subprocess.
199 209 """
200 210 #: string, name of test runner that will be called
201 211 runner = None
202 212 #: list, parameters for test runner
203 213 params = None
204 214 #: list, arguments of system call to be made to call test runner
205 215 call_args = None
206 216 #: list, process ids of subprocesses we start (for cleanup)
207 217 pids = None
208 218
209 219 def __init__(self, runner='iptest', params=None):
210 220 """Create new test runner."""
221 p = os.path
211 222 if runner == 'iptest':
212 # Find our own 'iptest' script OS-level entry point. Don't look
213 # system-wide, so we are sure we pick up *this one*. And pass
214 # through to subprocess call our own sys.argv
215 self.runner = tools.cmd2argv(os.path.abspath(__file__)) + \
216 sys.argv[1:]
223 if INSTALLED:
224 self.runner = tools.cmd2argv(
225 p.abspath(find_cmd('iptest'))) + sys.argv[1:]
226 else:
227 # Find our own 'iptest' script OS-level entry point. Don't
228 # look system-wide, so we are sure we pick up *this one*. And
229 # pass through to subprocess call our own sys.argv
230 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
231 script = p.join(ippath, 'iptest.py')
232 self.runner = tools.cmd2argv(script) + sys.argv[1:]
233
217 234 else:
218 self.runner = tools.cmd2argv(os.path.abspath(find_cmd('trial')))
235 # For trial, it needs to be installed system-wide
236 self.runner = tools.cmd2argv(p.abspath(find_cmd('trial')))
219 237 if params is None:
220 238 params = []
221 239 if isinstance(params, str):
222 240 params = [params]
223 241 self.params = params
224 242
225 243 # Assemble call
226 244 self.call_args = self.runner+self.params
227 245
228 246 # Store pids of anything we start to clean up on deletion, if possible
229 247 # (on posix only, since win32 has no os.kill)
230 248 self.pids = []
231 249
232 250 if sys.platform == 'win32':
233 251 def _run_cmd(self):
234 252 # On Windows, use os.system instead of subprocess.call, because I
235 253 # was having problems with subprocess and I just don't know enough
236 254 # about win32 to debug this reliably. Os.system may be the 'old
237 255 # fashioned' way to do it, but it works just fine. If someone
238 256 # later can clean this up that's fine, as long as the tests run
239 257 # reliably in win32.
240 258 return os.system(' '.join(self.call_args))
241 259 else:
242 260 def _run_cmd(self):
243 261 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
244 262 subp = subprocess.Popen(self.call_args)
245 263 self.pids.append(subp.pid)
246 264 # If this fails, the pid will be left in self.pids and cleaned up
247 265 # later, but if the wait call succeeds, then we can clear the
248 266 # stored pid.
249 267 retcode = subp.wait()
250 268 self.pids.pop()
251 269 return retcode
252 270
253 271 def run(self):
254 272 """Run the stored commands"""
255 273 try:
256 274 return self._run_cmd()
257 275 except:
258 276 import traceback
259 277 traceback.print_exc()
260 278 return 1 # signal failure
261 279
262 280 def __del__(self):
263 281 """Cleanup on exit by killing any leftover processes."""
264 282
265 283 if not hasattr(os, 'kill'):
266 284 return
267 285
268 286 for pid in self.pids:
269 287 try:
270 288 print 'Cleaning stale PID:', pid
271 289 os.kill(pid, signal.SIGKILL)
272 290 except OSError:
273 291 # This is just a best effort, if we fail or the process was
274 292 # really gone, ignore it.
275 293 pass
276 294
277 295
278 296 def make_runners():
279 297 """Define the top-level packages that need to be tested.
280 298 """
281 299
282 300 # Packages to be tested via nose, that only depend on the stdlib
283 301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
284 302 'scripts', 'testing', 'utils' ]
285 303 # The machinery in kernel needs twisted for real testing
286 304 trial_pkg_names = []
287 305
288 306 if have_wx:
289 307 nose_pkg_names.append('gui')
290 308
291 309 # And add twisted ones if conditions are met
292 310 if have_zi and have_twisted and have_foolscap:
293 311 # Note that we list the kernel here, though the bulk of it is
294 312 # twisted-based, because nose picks up doctests that twisted doesn't.
295 313 nose_pkg_names.append('kernel')
296 314 trial_pkg_names.append('kernel')
297 315
298 316 # For debugging this code, only load quick stuff
299 #nose_pkg_names = ['core'] # dbg
317 #nose_pkg_names = ['core', 'extensions'] # dbg
300 318 #trial_pkg_names = [] # dbg
301 319
302 320 # Make fully qualified package names prepending 'IPython.' to our name lists
303 321 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
304 322 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
305 323
306 324 # Make runners
307 325 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
308 326 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
309 327
310 328 return runners
311 329
312 330
313 331 def run_iptest():
314 332 """Run the IPython test suite using nose.
315 333
316 334 This function is called when this script is **not** called with the form
317 335 `iptest all`. It simply calls nose with appropriate command line flags
318 336 and accepts all of the standard nose arguments.
319 337 """
320 338
321 339 warnings.filterwarnings('ignore',
322 340 'This will be removed soon. Use IPython.testing.util instead')
323 341
324 342 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
325
326 # I don't fully understand why we need this one, but
327 # depending on what directory the test suite is run
328 # from, if we don't give it, 0 tests get run.
329 # Specifically, if the test suite is run from the
330 # source dir with an argument (like 'iptest.py
331 # IPython.core', 0 tests are run, even if the same call
332 # done in this directory works fine). It appears that
333 # if the requested package is in the current dir,
334 # nose bails early by default. Since it's otherwise
335 # harmless, leave it in by default.
336 '--traverse-namespace',
337
343
338 344 # Loading ipdoctest causes problems with Twisted, but
339 345 # our test suite runner now separates things and runs
340 346 # all Twisted tests with trial.
341 347 '--with-ipdoctest',
342 348 '--ipdoctest-tests','--ipdoctest-extension=txt',
343 349
344 350 # We add --exe because of setuptools' imbecility (it
345 351 # blindly does chmod +x on ALL files). Nose does the
346 352 # right thing and it tries to avoid executables,
347 353 # setuptools unfortunately forces our hand here. This
348 354 # has been discussed on the distutils list and the
349 355 # setuptools devs refuse to fix this problem!
350 356 '--exe',
351 357 ]
352 358
359 if nose.__version__ >= '0.11':
360 # I don't fully understand why we need this one, but depending on what
361 # directory the test suite is run from, if we don't give it, 0 tests
362 # get run. Specifically, if the test suite is run from the source dir
363 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
364 # even if the same call done in this directory works fine). It appears
365 # that if the requested package is in the current dir, nose bails early
366 # by default. Since it's otherwise harmless, leave it in by default
367 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
368 argv.append('--traverse-namespace')
353 369
354 370 # Construct list of plugins, omitting the existing doctest plugin, which
355 371 # ours replaces (and extends).
356 372 plugins = [IPythonDoctest(make_exclude())]
357 373 for p in nose.plugins.builtin.plugins:
358 374 plug = p()
359 375 if plug.name == 'doctest':
360 376 continue
361 377 plugins.append(plug)
362 378
363 379 # We need a global ipython running in this process
364 380 globalipapp.start_ipython()
365 381 # Now nose can run
366 382 TestProgram(argv=argv, plugins=plugins)
367 383
368 384
369 385 def run_iptestall():
370 386 """Run the entire IPython test suite by calling nose and trial.
371 387
372 388 This function constructs :class:`IPTester` instances for all IPython
373 389 modules and package and then runs each of them. This causes the modules
374 390 and packages of IPython to be tested each in their own subprocess using
375 391 nose or twisted.trial appropriately.
376 392 """
377 393
378 394 runners = make_runners()
379 395
380 396 # Run the test runners in a temporary dir so we can nuke it when finished
381 397 # to clean up any junk files left over by accident. This also makes it
382 398 # robust against being run in non-writeable directories by mistake, as the
383 399 # temp dir will always be user-writeable.
384 400 curdir = os.getcwd()
385 401 testdir = tempfile.gettempdir()
386 402 os.chdir(testdir)
387 403
388 404 # Run all test runners, tracking execution time
389 405 failed = []
390 406 t_start = time.time()
391 407 try:
392 408 for (name, runner) in runners:
393 409 print '*'*70
394 410 print 'IPython test group:',name
395 411 res = runner.run()
396 412 if res:
397 413 failed.append( (name, runner) )
398 414 finally:
399 415 os.chdir(curdir)
400 416 t_end = time.time()
401 417 t_tests = t_end - t_start
402 418 nrunners = len(runners)
403 419 nfail = len(failed)
404 420 # summarize results
405 421 print
406 422 print '*'*70
407 423 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
408 424 print
409 425 if not failed:
410 426 print 'OK'
411 427 else:
412 428 # If anything went wrong, point out what command to rerun manually to
413 429 # see the actual errors and individual summary
414 430 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
415 431 for name, failed_runner in failed:
416 432 print '-'*40
417 433 print 'Runner failed:',name
418 434 print 'You may wish to rerun this one individually, with:'
419 435 print ' '.join(failed_runner.call_args)
420 436 print
421 437
422 438
423 439 def main():
424 440 for arg in sys.argv[1:]:
425 441 if arg.startswith('IPython'):
426 442 # This is in-process
427 443 run_iptest()
428 444 else:
429 445 # This starts subprocesses
430 446 run_iptestall()
431 447
432 448
433 449 if __name__ == '__main__':
434 450 main()
@@ -1,317 +1,337 b''
1 1 """Generic testing tools that do NOT depend on Twisted.
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 7 Note: our testing package contains testing.util, which does depend on Twisted
8 8 and provides utilities for tests that manage Deferreds. All testing support
9 9 tools that only depend on nose, IPython or the standard library should go here
10 10 instead.
11 11
12 12
13 13 Authors
14 14 -------
15 15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 16 """
17 17
18 18 #*****************************************************************************
19 19 # Copyright (C) 2009 The IPython Development Team
20 20 #
21 21 # Distributed under the terms of the BSD License. The full license is in
22 22 # the file COPYING, distributed as part of this software.
23 23 #*****************************************************************************
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Required modules and packages
27 27 #-----------------------------------------------------------------------------
28 28 from __future__ import absolute_import
29 29
30 30 import os
31 31 import re
32 32 import sys
33 33 import tempfile
34 34
35 35 try:
36 36 # These tools are used by parts of the runtime, so we make the nose
37 37 # dependency optional at this point. Nose is a hard dependency to run the
38 38 # test suite, but NOT to use ipython itself.
39 39 import nose.tools as nt
40 40 has_nose = True
41 41 except ImportError:
42 42 has_nose = False
43 43
44 44 from IPython.utils import genutils, platutils
45 45
46 46 from . import decorators as dec
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Globals
50 50 #-----------------------------------------------------------------------------
51 51
52 # By default, we assume IPython has been installed. But if the test suite is
53 # being run from a source tree that has NOT been installed yet, this flag can
54 # be set to False by the entry point scripts, to let us know that we must call
55 # the source tree versions of the scripts which manipulate sys.path instead of
56 # assuming that things exist system-wide.
57 INSTALLED = True
58
52 59 # Make a bunch of nose.tools assert wrappers that can be used in test
53 60 # generators. This will expose an assert* function for each one in nose.tools.
54 61
55 62 _tpl = """
56 63 def %(name)s(*a,**kw):
57 64 return nt.%(name)s(*a,**kw)
58 65 """
59 66
60 67 if has_nose:
61 68 for _x in [a for a in dir(nt) if a.startswith('assert')]:
62 69 exec _tpl % dict(name=_x)
63 70
64 71 #-----------------------------------------------------------------------------
65 72 # Functions and classes
66 73 #-----------------------------------------------------------------------------
67 74
68 75 # The docstring for full_path doctests differently on win32 (different path
69 76 # separator) so just skip the doctest there. The example remains informative.
70 77 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
71 78
72 79 @doctest_deco
73 80 def full_path(startPath,files):
74 81 """Make full paths for all the listed files, based on startPath.
75 82
76 83 Only the base part of startPath is kept, since this routine is typically
77 84 used with a script's __file__ variable as startPath. The base of startPath
78 85 is then prepended to all the listed files, forming the output list.
79 86
80 87 Parameters
81 88 ----------
82 89 startPath : string
83 90 Initial path to use as the base for the results. This path is split
84 91 using os.path.split() and only its first component is kept.
85 92
86 93 files : string or list
87 94 One or more files.
88 95
89 96 Examples
90 97 --------
91 98
92 99 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
93 100 ['/foo/a.txt', '/foo/b.txt']
94 101
95 102 >>> full_path('/foo',['a.txt','b.txt'])
96 103 ['/a.txt', '/b.txt']
97 104
98 105 If a single file is given, the output is still a list:
99 106 >>> full_path('/foo','a.txt')
100 107 ['/a.txt']
101 108 """
102 109
103 110 files = genutils.list_strings(files)
104 111 base = os.path.split(startPath)[0]
105 112 return [ os.path.join(base,f) for f in files ]
106 113
107 114
108 115 def parse_test_output(txt):
109 116 """Parse the output of a test run and return errors, failures.
110 117
111 118 Parameters
112 119 ----------
113 120 txt : str
114 121 Text output of a test run, assumed to contain a line of one of the
115 122 following forms::
116 123 'FAILED (errors=1)'
117 124 'FAILED (failures=1)'
118 125 'FAILED (errors=1, failures=1)'
119 126
120 127 Returns
121 128 -------
122 129 nerr, nfail: number of errors and failures.
123 130 """
124 131
125 132 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
126 133 if err_m:
127 134 nerr = int(err_m.group(1))
128 135 nfail = 0
129 136 return nerr, nfail
130 137
131 138 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
132 139 if fail_m:
133 140 nerr = 0
134 141 nfail = int(fail_m.group(1))
135 142 return nerr, nfail
136 143
137 144 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
138 145 re.MULTILINE)
139 146 if both_m:
140 147 nerr = int(both_m.group(1))
141 148 nfail = int(both_m.group(2))
142 149 return nerr, nfail
143 150
144 151 # If the input didn't match any of these forms, assume no error/failures
145 152 return 0, 0
146 153
147 154
148 155 # So nose doesn't think this is a test
149 156 parse_test_output.__test__ = False
150 157
151 158
152 159 def cmd2argv(cmd):
153 160 r"""Take the path of a command and return a list (argv-style).
154 161
155 162 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
156 163 .com or .bat, and ['python', cmd] otherwise.
157 164
158 165 This is mostly a Windows utility, to deal with the fact that the scripts in
159 166 Windows get wrapped in .exe entry points, so we have to call them
160 167 differently.
161 168
162 169 Parameters
163 170 ----------
164 171 cmd : string
165 172 The path of the command.
166 173
167 174 Returns
168 175 -------
169 176 argv-style list.
170 177
171 178 Examples
172 179 --------
173 180 In [2]: cmd2argv('/usr/bin/ipython')
174 181 Out[2]: ['python', '/usr/bin/ipython']
175 182
176 183 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
177 184 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
178 185 """
179 186 ext = os.path.splitext(cmd)[1]
180 187 if ext in ['.exe', '.com', '.bat']:
181 188 return [cmd]
182 189 else:
183 190 return ['python', cmd]
184 191
185 192
186 193 def temp_pyfile(src, ext='.py'):
187 194 """Make a temporary python file, return filename and filehandle.
188 195
189 196 Parameters
190 197 ----------
191 198 src : string or list of strings (no need for ending newlines if list)
192 199 Source code to be written to the file.
193 200
194 201 ext : optional, string
195 202 Extension for the generated file.
196 203
197 204 Returns
198 205 -------
199 206 (filename, open filehandle)
200 207 It is the caller's responsibility to close the open file and unlink it.
201 208 """
202 209 fname = tempfile.mkstemp(ext)[1]
203 210 f = open(fname,'w')
204 211 f.write(src)
205 212 f.flush()
206 213 return fname, f
207 214
208 215
209 216 def default_argv():
210 217 """Return a valid default argv for creating testing instances of ipython"""
211 218
212 219 return ['--quick', # so no config file is loaded
213 220 # Other defaults to minimize side effects on stdout
214 221 '--colors=NoColor', '--no-term-title','--no-banner',
215 222 '--autocall=0']
216 223
217 224
218 225 def ipexec(fname, options=None):
219 226 """Utility to call 'ipython filename'.
220 227
221 228 Starts IPython witha minimal and safe configuration to make startup as fast
222 229 as possible.
223 230
224 231 Note that this starts IPython in a subprocess!
225 232
226 233 Parameters
227 234 ----------
228 235 fname : str
229 236 Name of file to be executed (should have .py or .ipy extension).
230 237
231 238 options : optional, list
232 239 Extra command-line flags to be passed to IPython.
233 240
234 241 Returns
235 242 -------
236 243 (stdout, stderr) of ipython subprocess.
237 244 """
238 245 if options is None: options = []
239 246
240 247 # For these subprocess calls, eliminate all prompt printing so we only see
241 248 # output from script execution
242 249 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
243 250 cmdargs = ' '.join(default_argv() + prompt_opts + options)
244 251
245 252 _ip = get_ipython()
246 253 test_dir = os.path.dirname(__file__)
247 254
248 255 # Find the ipython script from the package we're using, so that the test
249 256 # suite can be run from the source tree without an installed IPython
250 257 p = os.path
251 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
252 ipython_script = p.join(ippath, 'ipython.py')
253 ipython_cmd = 'python "%s"' % ipython_script
258 if INSTALLED:
259 ipython_cmd = platutils.find_cmd('ipython')
260 else:
261 ippath = p.abspath(p.join(p.dirname(__file__),'..','..'))
262 ipython_script = p.join(ippath, 'ipython.py')
263 ipython_cmd = 'python "%s"' % ipython_script
254 264 # Absolute path for filename
255 265 full_fname = p.join(test_dir, fname)
256 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
266 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
267 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
257 268 return genutils.getoutputerror(full_cmd)
258 269
259 270
260 def ipexec_validate(fname, expected_out, expected_err=None,
271 def ipexec_validate(fname, expected_out, expected_err='',
261 272 options=None):
262 273 """Utility to call 'ipython filename' and validate output/error.
263 274
264 275 This function raises an AssertionError if the validation fails.
265 276
266 277 Note that this starts IPython in a subprocess!
267 278
268 279 Parameters
269 280 ----------
270 281 fname : str
271 282 Name of the file to be executed (should have .py or .ipy extension).
272 283
273 284 expected_out : str
274 285 Expected stdout of the process.
275 286
276 287 expected_err : optional, str
277 288 Expected stderr of the process.
278 289
279 290 options : optional, list
280 291 Extra command-line flags to be passed to IPython.
281 292
282 293 Returns
283 294 -------
284 295 None
285 296 """
286 297
287 298 import nose.tools as nt
288 299
289 300 out, err = ipexec(fname)
301 #print 'OUT', out # dbg
302 #print 'ERR', err # dbg
303 # If there are any errors, we must check those befor stdout, as they may be
304 # more informative than simply having an empty stdout.
305 if err:
306 if expected_err:
307 nt.assert_equals(err.strip(), expected_err.strip())
308 else:
309 raise ValueError('Running file %r produced error: %r' %
310 (fname, err))
311 # If no errors or output on stderr was expected, match stdout
290 312 nt.assert_equals(out.strip(), expected_out.strip())
291 if expected_err:
292 nt.assert_equals(err.strip(), expected_err.strip())
293 313
294 314
295 315 class TempFileMixin(object):
296 316 """Utility class to create temporary Python/IPython files.
297 317
298 318 Meant as a mixin class for test cases."""
299 319
300 320 def mktmp(self, src, ext='.py'):
301 321 """Make a valid python temp file."""
302 322 fname, f = temp_pyfile(src, ext)
303 323 self.tmpfile = f
304 324 self.fname = fname
305 325
306 326 def teardown(self):
307 327 if hasattr(self, 'tmpfile'):
308 328 # If the tmpfile wasn't made because of skipped tests, like in
309 329 # win32, there's nothing to cleanup.
310 330 self.tmpfile.close()
311 331 try:
312 332 os.unlink(self.fname)
313 333 except:
314 334 # On Windows, even though we close the file, we still can't
315 335 # delete it. I have no clue why
316 336 pass
317 337
@@ -1,21 +1,26 b''
1 1 #!/usr/bin/env python
2 2 """Test script for IPython.
3 3
4 4 The actual ipython test script to be installed with 'python setup.py install'
5 5 is in './scripts' directory, and will test IPython from an importable
6 6 location.
7 7
8 8 This file is here (ipython source root directory) to facilitate non-root
9 9 'zero-installation testing and development' (just copy the source tree
10 10 somewhere and run iptest.py).
11 11
12 12 You can run this script directly, type -h to see all options."""
13 13
14 14 # Ensure that the imported IPython packages come from *THIS* IPython, not some
15 15 # other one that may exist system-wide
16 16 import os, sys
17 17 this_dir = os.path.dirname(os.path.abspath(__file__))
18 18 sys.path.insert(0, this_dir)
19 19
20 import IPython.testing.tools as t
21 import IPython.testing.iptest as ipt
22 t.INSTALLED = False
23 ipt.INSTALLED = False
24
20 25 # Now proceed with execution
21 26 execfile(os.path.join(this_dir, 'IPython', 'scripts', 'iptest'))
General Comments 0
You need to be logged in to leave comments. Login now