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