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