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