##// END OF EJS Templates
Adding comment about ticket https://bugs.launchpad.net/bugs/366982
Brian Granger -
Show More
@@ -1,288 +1,290 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 sys
25 import sys
26 import subprocess
26 import subprocess
27 import time
27 import time
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.core import TestProgram
31 from nose.core import TestProgram
32
32
33 from IPython.platutils import find_cmd
33 from IPython.platutils import find_cmd
34 from IPython.testing.plugin.ipdoctest import IPythonDoctest
34 from IPython.testing.plugin.ipdoctest import IPythonDoctest
35
35
36 pjoin = path.join
36 pjoin = path.join
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Logic for skipping doctests
39 # Logic for skipping doctests
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 def test_for(mod):
42 def test_for(mod):
43 """Test to see if mod is importable."""
43 """Test to see if mod is importable."""
44 try:
44 try:
45 __import__(mod)
45 __import__(mod)
46 except ImportError:
46 except ImportError:
47 return False
47 return False
48 else:
48 else:
49 return True
49 return True
50
50
51 have_curses = test_for('_curses')
51 have_curses = test_for('_curses')
52 have_wx = test_for('wx')
52 have_wx = test_for('wx')
53 have_zi = test_for('zope.interface')
53 have_zi = test_for('zope.interface')
54 have_twisted = test_for('twisted')
54 have_twisted = test_for('twisted')
55 have_foolscap = test_for('foolscap')
55 have_foolscap = test_for('foolscap')
56 have_objc = test_for('objc')
56 have_objc = test_for('objc')
57 have_pexpect = test_for('pexpect')
57 have_pexpect = test_for('pexpect')
58
58
59 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
59 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
60 # testing problems. We should strive to minimize the number of skipped
60 # testing problems. We should strive to minimize the number of skipped
61 # modules, since this means untested code. As the testing machinery
61 # modules, since this means untested code. As the testing machinery
62 # solidifies, this list should eventually become empty.
62 # solidifies, this list should eventually become empty.
63 EXCLUDE = [pjoin('IPython', 'external'),
63 EXCLUDE = [pjoin('IPython', 'external'),
64 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
64 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
65 pjoin('IPython_doctest_plugin'),
65 pjoin('IPython_doctest_plugin'),
66 pjoin('IPython', 'Gnuplot'),
66 pjoin('IPython', 'Gnuplot'),
67 pjoin('IPython', 'Extensions', 'ipy_'),
67 pjoin('IPython', 'Extensions', 'ipy_'),
68 pjoin('IPython', 'Extensions', 'clearcmd'),
68 pjoin('IPython', 'Extensions', 'clearcmd'),
69 pjoin('IPython', 'Extensions', 'PhysicalQInteractive'),
69 pjoin('IPython', 'Extensions', 'PhysicalQInteractive'),
70 pjoin('IPython', 'Extensions', 'scitedirector'),
70 pjoin('IPython', 'Extensions', 'scitedirector'),
71 pjoin('IPython', 'Extensions', 'numeric_formats'),
71 pjoin('IPython', 'Extensions', 'numeric_formats'),
72 pjoin('IPython', 'testing', 'attic'),
72 pjoin('IPython', 'testing', 'attic'),
73 pjoin('IPython', 'testing', 'tutils'),
73 pjoin('IPython', 'testing', 'tutils'),
74 pjoin('IPython', 'testing', 'tools'),
74 pjoin('IPython', 'testing', 'tools'),
75 pjoin('IPython', 'testing', 'mkdoctests')
75 pjoin('IPython', 'testing', 'mkdoctests')
76 ]
76 ]
77
77
78 if not have_wx:
78 if not have_wx:
79 EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid'))
79 EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid'))
80 EXCLUDE.append(pjoin('IPython', 'gui'))
80 EXCLUDE.append(pjoin('IPython', 'gui'))
81 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
81 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
82
82
83 if not have_objc:
83 if not have_objc:
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
85
85
86 if not have_curses:
86 if not have_curses:
87 EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse'))
87 EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse'))
88
88
89 if not sys.platform == 'win32':
89 if not sys.platform == 'win32':
90 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
90 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
91
91
92 # These have to be skipped on win32 because the use echo, rm, cd, etc.
93 # See ticket https://bugs.launchpad.net/bugs/366982
92 if sys.platform == 'win32':
94 if sys.platform == 'win32':
93 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
95 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
94 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
96 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
95
97
96 if not os.name == 'posix':
98 if not os.name == 'posix':
97 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
99 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
98
100
99 if not have_pexpect:
101 if not have_pexpect:
100 EXCLUDE.append(pjoin('IPython', 'irunner'))
102 EXCLUDE.append(pjoin('IPython', 'irunner'))
101
103
102 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
104 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
103 if sys.platform == 'win32':
105 if sys.platform == 'win32':
104 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
106 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
105
107
106
108
107 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
108 # Functions and classes
110 # Functions and classes
109 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
110
112
111 def run_iptest():
113 def run_iptest():
112 """Run the IPython test suite using nose.
114 """Run the IPython test suite using nose.
113
115
114 This function is called when this script is **not** called with the form
116 This function is called when this script is **not** called with the form
115 `iptest all`. It simply calls nose with appropriate command line flags
117 `iptest all`. It simply calls nose with appropriate command line flags
116 and accepts all of the standard nose arguments.
118 and accepts all of the standard nose arguments.
117 """
119 """
118
120
119 warnings.filterwarnings('ignore',
121 warnings.filterwarnings('ignore',
120 'This will be removed soon. Use IPython.testing.util instead')
122 'This will be removed soon. Use IPython.testing.util instead')
121
123
122 argv = sys.argv + [
124 argv = sys.argv + [
123 # Loading ipdoctest causes problems with Twisted.
125 # Loading ipdoctest causes problems with Twisted.
124 # I am removing this as a temporary fix to get the
126 # I am removing this as a temporary fix to get the
125 # test suite back into working shape. Our nose
127 # test suite back into working shape. Our nose
126 # plugin needs to be gone through with a fine
128 # plugin needs to be gone through with a fine
127 # toothed comb to find what is causing the problem.
129 # toothed comb to find what is causing the problem.
128 '--with-ipdoctest',
130 '--with-ipdoctest',
129 '--ipdoctest-tests','--ipdoctest-extension=txt',
131 '--ipdoctest-tests','--ipdoctest-extension=txt',
130 '--detailed-errors',
132 '--detailed-errors',
131
133
132 # We add --exe because of setuptools' imbecility (it
134 # We add --exe because of setuptools' imbecility (it
133 # blindly does chmod +x on ALL files). Nose does the
135 # blindly does chmod +x on ALL files). Nose does the
134 # right thing and it tries to avoid executables,
136 # right thing and it tries to avoid executables,
135 # setuptools unfortunately forces our hand here. This
137 # setuptools unfortunately forces our hand here. This
136 # has been discussed on the distutils list and the
138 # has been discussed on the distutils list and the
137 # setuptools devs refuse to fix this problem!
139 # setuptools devs refuse to fix this problem!
138 '--exe',
140 '--exe',
139 ]
141 ]
140
142
141 # Detect if any tests were required by explicitly calling an IPython
143 # Detect if any tests were required by explicitly calling an IPython
142 # submodule or giving a specific path
144 # submodule or giving a specific path
143 has_tests = False
145 has_tests = False
144 for arg in sys.argv:
146 for arg in sys.argv:
145 if 'IPython' in arg or arg.endswith('.py') or \
147 if 'IPython' in arg or arg.endswith('.py') or \
146 (':' in arg and '.py' in arg):
148 (':' in arg and '.py' in arg):
147 has_tests = True
149 has_tests = True
148 break
150 break
149
151
150 # If nothing was specifically requested, test full IPython
152 # If nothing was specifically requested, test full IPython
151 if not has_tests:
153 if not has_tests:
152 argv.append('IPython')
154 argv.append('IPython')
153
155
154 # Construct list of plugins, omitting the existing doctest plugin, which
156 # Construct list of plugins, omitting the existing doctest plugin, which
155 # ours replaces (and extends).
157 # ours replaces (and extends).
156 plugins = [IPythonDoctest(EXCLUDE)]
158 plugins = [IPythonDoctest(EXCLUDE)]
157 for p in nose.plugins.builtin.plugins:
159 for p in nose.plugins.builtin.plugins:
158 plug = p()
160 plug = p()
159 if plug.name == 'doctest':
161 if plug.name == 'doctest':
160 continue
162 continue
161
163
162 #print '*** adding plugin:',plug.name # dbg
164 #print '*** adding plugin:',plug.name # dbg
163 plugins.append(plug)
165 plugins.append(plug)
164
166
165 TestProgram(argv=argv,plugins=plugins)
167 TestProgram(argv=argv,plugins=plugins)
166
168
167
169
168 class IPTester(object):
170 class IPTester(object):
169 """Call that calls iptest or trial in a subprocess.
171 """Call that calls iptest or trial in a subprocess.
170 """
172 """
171 def __init__(self,runner='iptest',params=None):
173 def __init__(self,runner='iptest',params=None):
172 """ """
174 """ """
173 if runner == 'iptest':
175 if runner == 'iptest':
174 self.runner = ['iptest','-v']
176 self.runner = ['iptest','-v']
175 else:
177 else:
176 self.runner = [find_cmd('trial')]
178 self.runner = [find_cmd('trial')]
177 if params is None:
179 if params is None:
178 params = []
180 params = []
179 if isinstance(params,str):
181 if isinstance(params,str):
180 params = [params]
182 params = [params]
181 self.params = params
183 self.params = params
182
184
183 # Assemble call
185 # Assemble call
184 self.call_args = self.runner+self.params
186 self.call_args = self.runner+self.params
185
187
186 def run(self):
188 def run(self):
187 """Run the stored commands"""
189 """Run the stored commands"""
188 return subprocess.call(self.call_args)
190 return subprocess.call(self.call_args)
189
191
190
192
191 def make_runners():
193 def make_runners():
192 """Define the modules and packages that need to be tested.
194 """Define the modules and packages that need to be tested.
193 """
195 """
194
196
195 # This omits additional top-level modules that should not be doctested.
197 # This omits additional top-level modules that should not be doctested.
196 # XXX: Shell.py is also ommited because of a bug in the skip_doctest
198 # XXX: Shell.py is also ommited because of a bug in the skip_doctest
197 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
199 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
198 top_mod = \
200 top_mod = \
199 ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py',
201 ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py',
200 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py',
202 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py',
201 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py',
203 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py',
202 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
204 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
203 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
205 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
204 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py',
206 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py',
205 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py',
207 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py',
206 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py',
208 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py',
207 'shellglobals.py', 'strdispatch.py', 'twshell.py',
209 'shellglobals.py', 'strdispatch.py', 'twshell.py',
208 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py',
210 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py',
209 # See note above for why this is skipped
211 # See note above for why this is skipped
210 # 'Shell.py',
212 # 'Shell.py',
211 'winconsole.py']
213 'winconsole.py']
212
214
213 if have_pexpect:
215 if have_pexpect:
214 top_mod.append('irunner.py')
216 top_mod.append('irunner.py')
215
217
216 # These are tested by nose, so skip IPython.kernel
218 # These are tested by nose, so skip IPython.kernel
217 top_pack = ['config','Extensions','frontend',
219 top_pack = ['config','Extensions','frontend',
218 'testing','tests','tools','UserConfig']
220 'testing','tests','tools','UserConfig']
219
221
220 if have_wx:
222 if have_wx:
221 top_pack.append('gui')
223 top_pack.append('gui')
222
224
223 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
225 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
224 packages = ['IPython.%s' % m for m in top_pack ]
226 packages = ['IPython.%s' % m for m in top_pack ]
225
227
226 # Make runners
228 # Make runners
227 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
229 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
228
230
229 # Test IPython.kernel using trial if twisted is installed
231 # Test IPython.kernel using trial if twisted is installed
230 if have_zi and have_twisted and have_foolscap:
232 if have_zi and have_twisted and have_foolscap:
231 runners['trial'] = IPTester('trial',['IPython'])
233 runners['trial'] = IPTester('trial',['IPython'])
232
234
233 runners['modules'] = IPTester(params=modules)
235 runners['modules'] = IPTester(params=modules)
234
236
235 return runners
237 return runners
236
238
237
239
238 def run_iptestall():
240 def run_iptestall():
239 """Run the entire IPython test suite by calling nose and trial.
241 """Run the entire IPython test suite by calling nose and trial.
240
242
241 This function constructs :class:`IPTester` instances for all IPython
243 This function constructs :class:`IPTester` instances for all IPython
242 modules and package and then runs each of them. This causes the modules
244 modules and package and then runs each of them. This causes the modules
243 and packages of IPython to be tested each in their own subprocess using
245 and packages of IPython to be tested each in their own subprocess using
244 nose or twisted.trial appropriately.
246 nose or twisted.trial appropriately.
245 """
247 """
246 runners = make_runners()
248 runners = make_runners()
247 # Run all test runners, tracking execution time
249 # Run all test runners, tracking execution time
248 failed = {}
250 failed = {}
249 t_start = time.time()
251 t_start = time.time()
250 for name,runner in runners.iteritems():
252 for name,runner in runners.iteritems():
251 print '*'*77
253 print '*'*77
252 print 'IPython test set:',name
254 print 'IPython test set:',name
253 res = runner.run()
255 res = runner.run()
254 if res:
256 if res:
255 failed[name] = res
257 failed[name] = res
256 t_end = time.time()
258 t_end = time.time()
257 t_tests = t_end - t_start
259 t_tests = t_end - t_start
258 nrunners = len(runners)
260 nrunners = len(runners)
259 nfail = len(failed)
261 nfail = len(failed)
260 # summarize results
262 # summarize results
261 print
263 print
262 print '*'*77
264 print '*'*77
263 print 'Ran %s test sets in %.3fs' % (nrunners, t_tests)
265 print 'Ran %s test sets in %.3fs' % (nrunners, t_tests)
264 print
266 print
265 if not failed:
267 if not failed:
266 print 'OK'
268 print 'OK'
267 else:
269 else:
268 # If anything went wrong, point out what command to rerun manually to
270 # If anything went wrong, point out what command to rerun manually to
269 # see the actual errors and individual summary
271 # see the actual errors and individual summary
270 print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners)
272 print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners)
271 for name in failed:
273 for name in failed:
272 failed_runner = runners[name]
274 failed_runner = runners[name]
273 print '-'*40
275 print '-'*40
274 print 'Runner failed:',name
276 print 'Runner failed:',name
275 print 'You may wish to rerun this one individually, with:'
277 print 'You may wish to rerun this one individually, with:'
276 print ' '.join(failed_runner.call_args)
278 print ' '.join(failed_runner.call_args)
277 print
279 print
278
280
279
281
280 def main():
282 def main():
281 if sys.argv[1] == 'all':
283 if sys.argv[1] == 'all':
282 run_iptestall()
284 run_iptestall()
283 else:
285 else:
284 run_iptest()
286 run_iptest()
285
287
286
288
287 if __name__ == '__main__':
289 if __name__ == '__main__':
288 main() No newline at end of file
290 main()
General Comments 0
You need to be logged in to leave comments. Login now