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