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