##// END OF EJS Templates
Fix tests to return consistent results regardless of how they are called....
Fernando Perez -
Show More
@@ -1,420 +1,432 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 #print 'ipp:', ippath # dbg
42 #import IPython; print 'IP file:', IPython.__file__ # dbg
43 41
44 42 # Note: monkeypatch!
45 43 # We need to monkeypatch a small problem in nose itself first, before importing
46 44 # it for actual use. This should get into nose upstream, but its release cycle
47 45 # is slow and we need it for our parametric tests to work correctly.
48 46 from IPython.testing import nosepatch
49 47 # Now, proceed to import nose itself
50 48 import nose.plugins.builtin
51 49 from nose.core import TestProgram
52 50
53 51 # Our own imports
54 52 from IPython.utils import genutils
55 53 from IPython.utils.platutils import find_cmd, FindCmdError
56 54 from IPython.testing import globalipapp
57 55 from IPython.testing import tools
58 56 from IPython.testing.plugin.ipdoctest import IPythonDoctest
59 57
60 58 pjoin = path.join
61 59
62 60 #-----------------------------------------------------------------------------
63 61 # Warnings control
64 62 #-----------------------------------------------------------------------------
65 63 # Twisted generates annoying warnings with Python 2.6, as will do other code
66 64 # that imports 'sets' as of today
67 65 warnings.filterwarnings('ignore', 'the sets module is deprecated',
68 66 DeprecationWarning )
69 67
70 68 # This one also comes from Twisted
71 69 warnings.filterwarnings('ignore', 'the sha module is deprecated',
72 70 DeprecationWarning)
73 71
74 72 # Wx on Fedora11 spits these out
75 73 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
76 74 UserWarning)
77 75
78 76 #-----------------------------------------------------------------------------
79 77 # Logic for skipping doctests
80 78 #-----------------------------------------------------------------------------
81 79
82 80 def test_for(mod):
83 81 """Test to see if mod is importable."""
84 82 try:
85 83 __import__(mod)
86 84 except ImportError:
87 85 return False
88 86 else:
89 87 return True
90 88
91 89
92 90 have_curses = test_for('_curses')
93 91 have_wx = test_for('wx')
94 92 have_wx_aui = test_for('wx.aui')
95 93 have_zi = test_for('zope.interface')
96 94 have_twisted = test_for('twisted')
97 95 have_foolscap = test_for('foolscap')
98 96 have_objc = test_for('objc')
99 97 have_pexpect = test_for('pexpect')
100 98 have_gtk = test_for('gtk')
101 99 have_gobject = test_for('gobject')
102 100
103 101
104 102 def make_exclude():
105 103 """Make patterns of modules and packages to exclude from testing.
106 104
107 105 For the IPythonDoctest plugin, we need to exclude certain patterns that
108 106 cause testing problems. We should strive to minimize the number of
109 107 skipped modules, since this means untested code. As the testing
110 108 machinery solidifies, this list should eventually become empty.
111 109 These modules and packages will NOT get scanned by nose at all for tests.
112 110 """
113 111 # Simple utility to make IPython paths more readably, we need a lot of
114 112 # these below
115 113 ipjoin = lambda *paths: pjoin('IPython', *paths)
116 114
117 115 exclusions = [ipjoin('external'),
118 116 ipjoin('frontend', 'process', 'winprocess.py'),
119 117 # Deprecated old Shell and iplib modules, skip to avoid
120 118 # warnings
121 119 ipjoin('Shell'),
122 120 ipjoin('iplib'),
123 121 pjoin('IPython_doctest_plugin'),
124 122 ipjoin('quarantine'),
125 123 ipjoin('deathrow'),
126 124 ipjoin('testing', 'attic'),
127 125 # This guy is probably attic material
128 126 ipjoin('testing', 'mkdoctests'),
129 127 # Testing inputhook will need a lot of thought, to figure out
130 128 # how to have tests that don't lock up with the gui event
131 129 # loops in the picture
132 130 ipjoin('lib', 'inputhook'),
133 131 # Config files aren't really importable stand-alone
134 132 ipjoin('config', 'default'),
135 133 ipjoin('config', 'profile'),
136 134 ]
137 135
138 136 if not have_wx:
139 137 exclusions.append(ipjoin('gui'))
140 138 exclusions.append(ipjoin('frontend', 'wx'))
141 139 exclusions.append(ipjoin('lib', 'inputhookwx'))
142 140
143 141 if not have_gtk or not have_gobject:
144 142 exclusions.append(ipjoin('lib', 'inputhookgtk'))
145 143
146 144 if not have_wx_aui:
147 145 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
148 146
149 147 if not have_objc:
150 148 exclusions.append(ipjoin('frontend', 'cocoa'))
151 149
152 150 if not sys.platform == 'win32':
153 151 exclusions.append(ipjoin('utils', 'platutils_win32'))
154 152
155 153 # These have to be skipped on win32 because the use echo, rm, cd, etc.
156 154 # See ticket https://bugs.launchpad.net/bugs/366982
157 155 if sys.platform == 'win32':
158 156 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
159 157 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
160 158
161 159 if not os.name == 'posix':
162 160 exclusions.append(ipjoin('utils', 'platutils_posix'))
163 161
164 162 if not have_pexpect:
165 163 exclusions.extend([ipjoin('scripts', 'irunner'),
166 164 ipjoin('lib', 'irunner')])
167 165
168 166 # This is scary. We still have things in frontend and testing that
169 167 # are being tested by nose that use twisted. We need to rethink
170 168 # how we are isolating dependencies in testing.
171 169 if not (have_twisted and have_zi and have_foolscap):
172 170 exclusions.extend(
173 171 [ipjoin('frontend', 'asyncfrontendbase'),
174 172 ipjoin('frontend', 'prefilterfrontend'),
175 173 ipjoin('frontend', 'frontendbase'),
176 174 ipjoin('frontend', 'linefrontendbase'),
177 175 ipjoin('frontend', 'tests', 'test_linefrontend'),
178 176 ipjoin('frontend', 'tests', 'test_frontendbase'),
179 177 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
180 178 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
181 179 ipjoin('testing', 'parametric'),
182 180 ipjoin('testing', 'util'),
183 181 ipjoin('testing', 'tests', 'test_decorators_trial'),
184 182 ] )
185 183
186 184 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
187 185 if sys.platform == 'win32':
188 186 exclusions = [s.replace('\\','\\\\') for s in exclusions]
189 187
190 188 return exclusions
191 189
192 190
193 191 #-----------------------------------------------------------------------------
194 192 # Functions and classes
195 193 #-----------------------------------------------------------------------------
196 194
197 195 class IPTester(object):
198 196 """Call that calls iptest or trial in a subprocess.
199 197 """
200 198 #: string, name of test runner that will be called
201 199 runner = None
202 200 #: list, parameters for test runner
203 201 params = None
204 202 #: list, arguments of system call to be made to call test runner
205 203 call_args = None
206 204 #: list, process ids of subprocesses we start (for cleanup)
207 205 pids = None
208 206
209 207 def __init__(self, runner='iptest', params=None):
210 208 """Create new test runner."""
211 209 if runner == 'iptest':
212 210 # Find our own 'iptest' script OS-level entry point. Don't look
213 211 # system-wide, so we are sure we pick up *this one*. And pass
214 212 # through to subprocess call our own sys.argv
215 213 self.runner = tools.cmd2argv(os.path.abspath(__file__)) + \
216 214 sys.argv[1:]
217 215 else:
218 216 self.runner = tools.cmd2argv(os.path.abspath(find_cmd('trial')))
219 217 if params is None:
220 218 params = []
221 219 if isinstance(params, str):
222 220 params = [params]
223 221 self.params = params
224 222
225 223 # Assemble call
226 224 self.call_args = self.runner+self.params
227 225
228 226 # Store pids of anything we start to clean up on deletion, if possible
229 227 # (on posix only, since win32 has no os.kill)
230 228 self.pids = []
231 229
232 230 if sys.platform == 'win32':
233 231 def _run_cmd(self):
234 232 # On Windows, use os.system instead of subprocess.call, because I
235 233 # was having problems with subprocess and I just don't know enough
236 234 # about win32 to debug this reliably. Os.system may be the 'old
237 235 # fashioned' way to do it, but it works just fine. If someone
238 236 # later can clean this up that's fine, as long as the tests run
239 237 # reliably in win32.
240 238 return os.system(' '.join(self.call_args))
241 239 else:
242 240 def _run_cmd(self):
241 #print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
243 242 subp = subprocess.Popen(self.call_args)
244 243 self.pids.append(subp.pid)
245 244 # If this fails, the pid will be left in self.pids and cleaned up
246 245 # later, but if the wait call succeeds, then we can clear the
247 246 # stored pid.
248 247 retcode = subp.wait()
249 248 self.pids.pop()
250 249 return retcode
251 250
252 251 def run(self):
253 252 """Run the stored commands"""
254 253 try:
255 254 return self._run_cmd()
256 255 except:
257 256 import traceback
258 257 traceback.print_exc()
259 258 return 1 # signal failure
260 259
261 260 def __del__(self):
262 261 """Cleanup on exit by killing any leftover processes."""
263 262
264 263 if not hasattr(os, 'kill'):
265 264 return
266 265
267 266 for pid in self.pids:
268 267 try:
269 268 print 'Cleaning stale PID:', pid
270 269 os.kill(pid, signal.SIGKILL)
271 270 except OSError:
272 271 # This is just a best effort, if we fail or the process was
273 272 # really gone, ignore it.
274 273 pass
275 274
276 275
277 276 def make_runners():
278 277 """Define the top-level packages that need to be tested.
279 278 """
280 279
281 280 # Packages to be tested via nose, that only depend on the stdlib
282 281 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
283 282 'scripts', 'testing', 'utils' ]
284 283 # The machinery in kernel needs twisted for real testing
285 284 trial_pkg_names = []
286 285
287 286 if have_wx:
288 287 nose_pkg_names.append('gui')
289 288
290 289 # And add twisted ones if conditions are met
291 290 if have_zi and have_twisted and have_foolscap:
292 291 # Note that we list the kernel here, though the bulk of it is
293 292 # twisted-based, because nose picks up doctests that twisted doesn't.
294 293 nose_pkg_names.append('kernel')
295 294 trial_pkg_names.append('kernel')
296 295
297 296 # For debugging this code, only load quick stuff
298 #nose_pkg_names = ['config', 'utils'] # dbg
297 #nose_pkg_names = ['core'] # dbg
299 298 #trial_pkg_names = [] # dbg
300 299
301 300 # Make fully qualified package names prepending 'IPython.' to our name lists
302 301 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
303 302 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
304 303
305 304 # Make runners
306 305 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
307 306 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
308 307
309 308 return runners
310 309
311 310
312 311 def run_iptest():
313 312 """Run the IPython test suite using nose.
314 313
315 314 This function is called when this script is **not** called with the form
316 315 `iptest all`. It simply calls nose with appropriate command line flags
317 316 and accepts all of the standard nose arguments.
318 317 """
319 318
320 319 warnings.filterwarnings('ignore',
321 320 'This will be removed soon. Use IPython.testing.util instead')
322 321
323 argv = sys.argv + [ '--detailed-errors',
322 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
323
324 # I don't fully understand why we need this one, but
325 # depending on what directory the test suite is run
326 # from, if we don't give it, 0 tests get run.
327 # Specifically, if the test suite is run from the
328 # source dir with an argument (like 'iptest.py
329 # IPython.core', 0 tests are run, even if the same call
330 # done in this directory works fine). It appears that
331 # if the requested package is in the current dir,
332 # nose bails early by default. Since it's otherwise
333 # harmless, leave it in by default.
334 '--traverse-namespace',
335
324 336 # Loading ipdoctest causes problems with Twisted, but
325 337 # our test suite runner now separates things and runs
326 338 # all Twisted tests with trial.
327 339 '--with-ipdoctest',
328 340 '--ipdoctest-tests','--ipdoctest-extension=txt',
329 341
330 342 # We add --exe because of setuptools' imbecility (it
331 343 # blindly does chmod +x on ALL files). Nose does the
332 344 # right thing and it tries to avoid executables,
333 345 # setuptools unfortunately forces our hand here. This
334 346 # has been discussed on the distutils list and the
335 347 # setuptools devs refuse to fix this problem!
336 348 '--exe',
337 349 ]
338 350
339 351
340 352 # Construct list of plugins, omitting the existing doctest plugin, which
341 353 # ours replaces (and extends).
342 354 plugins = [IPythonDoctest(make_exclude())]
343 355 for p in nose.plugins.builtin.plugins:
344 356 plug = p()
345 357 if plug.name == 'doctest':
346 358 continue
347 359 plugins.append(plug)
348 360
349 361 # We need a global ipython running in this process
350 362 globalipapp.start_ipython()
351 363 # Now nose can run
352 364 TestProgram(argv=argv, plugins=plugins)
353 365
354 366
355 367 def run_iptestall():
356 368 """Run the entire IPython test suite by calling nose and trial.
357 369
358 370 This function constructs :class:`IPTester` instances for all IPython
359 371 modules and package and then runs each of them. This causes the modules
360 372 and packages of IPython to be tested each in their own subprocess using
361 373 nose or twisted.trial appropriately.
362 374 """
363 375
364 376 runners = make_runners()
365 377
366 378 # Run the test runners in a temporary dir so we can nuke it when finished
367 379 # to clean up any junk files left over by accident. This also makes it
368 380 # robust against being run in non-writeable directories by mistake, as the
369 381 # temp dir will always be user-writeable.
370 382 curdir = os.getcwd()
371 383 testdir = tempfile.gettempdir()
372 384 os.chdir(testdir)
373 385
374 386 # Run all test runners, tracking execution time
375 387 failed = []
376 388 t_start = time.time()
377 389 try:
378 390 for (name, runner) in runners:
379 391 print '*'*70
380 392 print 'IPython test group:',name
381 393 res = runner.run()
382 394 if res:
383 395 failed.append( (name, runner) )
384 396 finally:
385 397 os.chdir(curdir)
386 398 t_end = time.time()
387 399 t_tests = t_end - t_start
388 400 nrunners = len(runners)
389 401 nfail = len(failed)
390 402 # summarize results
391 403 print
392 404 print '*'*70
393 405 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
394 406 print
395 407 if not failed:
396 408 print 'OK'
397 409 else:
398 410 # If anything went wrong, point out what command to rerun manually to
399 411 # see the actual errors and individual summary
400 412 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
401 413 for name, failed_runner in failed:
402 414 print '-'*40
403 415 print 'Runner failed:',name
404 416 print 'You may wish to rerun this one individually, with:'
405 417 print ' '.join(failed_runner.call_args)
406 418 print
407 419
408 420
409 421 def main():
410 422 for arg in sys.argv[1:]:
411 423 if arg.startswith('IPython'):
412 424 # This is in-process
413 425 run_iptest()
414 426 else:
415 427 # This starts subprocesses
416 428 run_iptestall()
417 429
418 430
419 431 if __name__ == '__main__':
420 432 main()
@@ -1,61 +1,61 b''
1 1 # -*- coding: utf-8 -*-
2 2 """ Platform specific utility functions, posix version
3 3
4 4 Importing this module directly is not portable - rather, import platutils
5 5 to use these functions in platform agnostic fashion.
6 6 """
7 7
8 8 #*****************************************************************************
9 9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #*****************************************************************************
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 from __future__ import absolute_import
18 18
19 19 import sys
20 20 import os
21 21
22 22 from .baseutils import getoutputerror
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Globals
26 26 #-----------------------------------------------------------------------------
27 27
28 28 ignore_termtitle = True
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _dummy_op(*a, **b):
35 35 """ A no-op function """
36 36
37 37
38 38 def _set_term_title_xterm(title):
39 39 """ Change virtual terminal title in xterm-workalikes """
40 40 sys.stdout.write('\033]0;%s\007' % title)
41 41
42 42 TERM = os.environ.get('TERM','')
43 43
44 44 if (TERM == 'xterm') or (TERM == 'xterm-color'):
45 45 set_term_title = _set_term_title_xterm
46 46 else:
47 47 set_term_title = _dummy_op
48 48
49 49
50 50 def find_cmd(cmd):
51 51 """Find the full path to a command using which."""
52 return getoutputerror('which %s' % cmd)[0]
52 return getoutputerror('/usr/bin/env which %s' % cmd)[0]
53 53
54 54
55 55 def get_long_path_name(path):
56 56 """Dummy no-op."""
57 57 return path
58 58
59 59
60 60 def term_clear():
61 61 os.system('clear')
@@ -1,17 +1,21 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 is in './scripts' directory. This file is here (ipython source root directory)
6 to facilitate non-root 'zero-installation testing' (just copy the source tree
7 somewhere and run ipython.py) and development.
5 is in './scripts' directory, and will test IPython from an importable
6 location.
7
8 This file is here (ipython source root directory) to facilitate non-root
9 'zero-installation testing and development' (just copy the source tree
10 somewhere and run iptest.py).
8 11
9 12 You can run this script directly, type -h to see all options."""
10 13
11 # Ensure that the imported IPython is the local one, not a system-wide one
14 # Ensure that the imported IPython packages come from *THIS* IPython, not some
15 # other one that may exist system-wide
12 16 import os, sys
13 17 this_dir = os.path.dirname(os.path.abspath(__file__))
14 18 sys.path.insert(0, this_dir)
15 19
16 20 # Now proceed with execution
17 21 execfile(os.path.join(this_dir, 'IPython', 'scripts', 'iptest'))
General Comments 0
You need to be logged in to leave comments. Login now