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