##// END OF EJS Templates
Fixes for test suite in win32 when all dependencies (esp. Twisted) are...
Fernando Perez -
Show More
@@ -1,320 +1,324 b''
1 """Decorators for labeling test objects.
1 """Decorators for labeling test objects.
2
2
3 Decorators that merely return a modified version of the original function
3 Decorators that merely return a modified version of the original function
4 object are straightforward. Decorators that return a new function object need
4 object are straightforward. Decorators that return a new function object need
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 decorator, in order to preserve metadata such as function name, setup and
6 decorator, in order to preserve metadata such as function name, setup and
7 teardown functions and so on - see nose.tools for more information.
7 teardown functions and so on - see nose.tools for more information.
8
8
9 This module provides a set of useful decorators meant to be ready to use in
9 This module provides a set of useful decorators meant to be ready to use in
10 your own tests. See the bottom of the file for the ready-made ones, and if you
10 your own tests. See the bottom of the file for the ready-made ones, and if you
11 find yourself writing a new one that may be of generic use, add it here.
11 find yourself writing a new one that may be of generic use, add it here.
12
12
13 Included decorators:
13 Included decorators:
14
14
15
15
16 Lightweight testing that remains unittest-compatible.
16 Lightweight testing that remains unittest-compatible.
17
17
18 - @parametric, for parametric test support that is vastly easier to use than
18 - @parametric, for parametric test support that is vastly easier to use than
19 nose's for debugging. With ours, if a test fails, the stack under inspection
19 nose's for debugging. With ours, if a test fails, the stack under inspection
20 is that of the test and not that of the test framework.
20 is that of the test and not that of the test framework.
21
21
22 - An @as_unittest decorator can be used to tag any normal parameter-less
22 - An @as_unittest decorator can be used to tag any normal parameter-less
23 function as a unittest TestCase. Then, both nose and normal unittest will
23 function as a unittest TestCase. Then, both nose and normal unittest will
24 recognize it as such. This will make it easier to migrate away from Nose if
24 recognize it as such. This will make it easier to migrate away from Nose if
25 we ever need/want to while maintaining very lightweight tests.
25 we ever need/want to while maintaining very lightweight tests.
26
26
27 NOTE: This file contains IPython-specific decorators and imports the
27 NOTE: This file contains IPython-specific decorators and imports the
28 numpy.testing.decorators file, which we've copied verbatim. Any of our own
28 numpy.testing.decorators file, which we've copied verbatim. Any of our own
29 code will be added at the bottom if we end up extending this.
29 code will be added at the bottom if we end up extending this.
30
30
31 Authors
31 Authors
32 -------
32 -------
33
33
34 - Fernando Perez <Fernando.Perez@berkeley.edu>
34 - Fernando Perez <Fernando.Perez@berkeley.edu>
35 """
35 """
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Copyright (C) 2009-2010 The IPython Development Team
38 # Copyright (C) 2009-2010 The IPython Development Team
39 #
39 #
40 # Distributed under the terms of the BSD License. The full license is in
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
41 # the file COPYING, distributed as part of this software.
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Imports
45 # Imports
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 # Stdlib imports
48 # Stdlib imports
49 import inspect
49 import inspect
50 import sys
50 import sys
51 import unittest
51 import unittest
52
52
53 # Third-party imports
53 # Third-party imports
54
54
55 # This is Michele Simionato's decorator module, kept verbatim.
55 # This is Michele Simionato's decorator module, kept verbatim.
56 from IPython.external.decorator import decorator, update_wrapper
56 from IPython.external.decorator import decorator, update_wrapper
57
57
58 # We already have python3-compliant code for parametric tests
58 # We already have python3-compliant code for parametric tests
59 if sys.version[0]=='2':
59 if sys.version[0]=='2':
60 from _paramtestpy2 import parametric, ParametricTestCase
60 from _paramtestpy2 import parametric, ParametricTestCase
61 else:
61 else:
62 from _paramtestpy3 import parametric, ParametricTestCase
62 from _paramtestpy3 import parametric, ParametricTestCase
63
63
64 # Expose the unittest-driven decorators
64 # Expose the unittest-driven decorators
65 from ipunittest import ipdoctest, ipdocstring
65 from ipunittest import ipdoctest, ipdocstring
66
66
67 # Grab the numpy-specific decorators which we keep in a file that we
67 # Grab the numpy-specific decorators which we keep in a file that we
68 # occasionally update from upstream: decorators.py is a copy of
68 # occasionally update from upstream: decorators.py is a copy of
69 # numpy.testing.decorators, we expose all of it here.
69 # numpy.testing.decorators, we expose all of it here.
70 from IPython.external.decorators import *
70 from IPython.external.decorators import *
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # Classes and functions
73 # Classes and functions
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76 # Simple example of the basic idea
76 # Simple example of the basic idea
77 def as_unittest(func):
77 def as_unittest(func):
78 """Decorator to make a simple function into a normal test via unittest."""
78 """Decorator to make a simple function into a normal test via unittest."""
79 class Tester(unittest.TestCase):
79 class Tester(unittest.TestCase):
80 def test(self):
80 def test(self):
81 func()
81 func()
82
82
83 Tester.__name__ = func.__name__
83 Tester.__name__ = func.__name__
84
84
85 return Tester
85 return Tester
86
86
87 # Utility functions
87 # Utility functions
88
88
89 def apply_wrapper(wrapper,func):
89 def apply_wrapper(wrapper,func):
90 """Apply a wrapper to a function for decoration.
90 """Apply a wrapper to a function for decoration.
91
91
92 This mixes Michele Simionato's decorator tool with nose's make_decorator,
92 This mixes Michele Simionato's decorator tool with nose's make_decorator,
93 to apply a wrapper in a decorator so that all nose attributes, as well as
93 to apply a wrapper in a decorator so that all nose attributes, as well as
94 function signature and other properties, survive the decoration cleanly.
94 function signature and other properties, survive the decoration cleanly.
95 This will ensure that wrapped functions can still be well introspected via
95 This will ensure that wrapped functions can still be well introspected via
96 IPython, for example.
96 IPython, for example.
97 """
97 """
98 import nose.tools
98 import nose.tools
99
99
100 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
100 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
101
101
102
102
103 def make_label_dec(label,ds=None):
103 def make_label_dec(label,ds=None):
104 """Factory function to create a decorator that applies one or more labels.
104 """Factory function to create a decorator that applies one or more labels.
105
105
106 Parameters
106 Parameters
107 ----------
107 ----------
108 label : string or sequence
108 label : string or sequence
109 One or more labels that will be applied by the decorator to the functions
109 One or more labels that will be applied by the decorator to the functions
110 it decorates. Labels are attributes of the decorated function with their
110 it decorates. Labels are attributes of the decorated function with their
111 value set to True.
111 value set to True.
112
112
113 ds : string
113 ds : string
114 An optional docstring for the resulting decorator. If not given, a
114 An optional docstring for the resulting decorator. If not given, a
115 default docstring is auto-generated.
115 default docstring is auto-generated.
116
116
117 Returns
117 Returns
118 -------
118 -------
119 A decorator.
119 A decorator.
120
120
121 Examples
121 Examples
122 --------
122 --------
123
123
124 A simple labeling decorator:
124 A simple labeling decorator:
125 >>> slow = make_label_dec('slow')
125 >>> slow = make_label_dec('slow')
126 >>> print slow.__doc__
126 >>> print slow.__doc__
127 Labels a test as 'slow'.
127 Labels a test as 'slow'.
128
128
129 And one that uses multiple labels and a custom docstring:
129 And one that uses multiple labels and a custom docstring:
130 >>> rare = make_label_dec(['slow','hard'],
130 >>> rare = make_label_dec(['slow','hard'],
131 ... "Mix labels 'slow' and 'hard' for rare tests.")
131 ... "Mix labels 'slow' and 'hard' for rare tests.")
132 >>> print rare.__doc__
132 >>> print rare.__doc__
133 Mix labels 'slow' and 'hard' for rare tests.
133 Mix labels 'slow' and 'hard' for rare tests.
134
134
135 Now, let's test using this one:
135 Now, let's test using this one:
136 >>> @rare
136 >>> @rare
137 ... def f(): pass
137 ... def f(): pass
138 ...
138 ...
139 >>>
139 >>>
140 >>> f.slow
140 >>> f.slow
141 True
141 True
142 >>> f.hard
142 >>> f.hard
143 True
143 True
144 """
144 """
145
145
146 if isinstance(label,basestring):
146 if isinstance(label,basestring):
147 labels = [label]
147 labels = [label]
148 else:
148 else:
149 labels = label
149 labels = label
150
150
151 # Validate that the given label(s) are OK for use in setattr() by doing a
151 # Validate that the given label(s) are OK for use in setattr() by doing a
152 # dry run on a dummy function.
152 # dry run on a dummy function.
153 tmp = lambda : None
153 tmp = lambda : None
154 for label in labels:
154 for label in labels:
155 setattr(tmp,label,True)
155 setattr(tmp,label,True)
156
156
157 # This is the actual decorator we'll return
157 # This is the actual decorator we'll return
158 def decor(f):
158 def decor(f):
159 for label in labels:
159 for label in labels:
160 setattr(f,label,True)
160 setattr(f,label,True)
161 return f
161 return f
162
162
163 # Apply the user's docstring, or autogenerate a basic one
163 # Apply the user's docstring, or autogenerate a basic one
164 if ds is None:
164 if ds is None:
165 ds = "Labels a test as %r." % label
165 ds = "Labels a test as %r." % label
166 decor.__doc__ = ds
166 decor.__doc__ = ds
167
167
168 return decor
168 return decor
169
169
170
170
171 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
171 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
172 # preserve function metadata better and allows the skip condition to be a
172 # preserve function metadata better and allows the skip condition to be a
173 # callable.
173 # callable.
174 def skipif(skip_condition, msg=None):
174 def skipif(skip_condition, msg=None):
175 ''' Make function raise SkipTest exception if skip_condition is true
175 ''' Make function raise SkipTest exception if skip_condition is true
176
176
177 Parameters
177 Parameters
178 ----------
178 ----------
179 skip_condition : bool or callable.
179 skip_condition : bool or callable.
180 Flag to determine whether to skip test. If the condition is a
180 Flag to determine whether to skip test. If the condition is a
181 callable, it is used at runtime to dynamically make the decision. This
181 callable, it is used at runtime to dynamically make the decision. This
182 is useful for tests that may require costly imports, to delay the cost
182 is useful for tests that may require costly imports, to delay the cost
183 until the test suite is actually executed.
183 until the test suite is actually executed.
184 msg : string
184 msg : string
185 Message to give on raising a SkipTest exception
185 Message to give on raising a SkipTest exception
186
186
187 Returns
187 Returns
188 -------
188 -------
189 decorator : function
189 decorator : function
190 Decorator, which, when applied to a function, causes SkipTest
190 Decorator, which, when applied to a function, causes SkipTest
191 to be raised when the skip_condition was True, and the function
191 to be raised when the skip_condition was True, and the function
192 to be called normally otherwise.
192 to be called normally otherwise.
193
193
194 Notes
194 Notes
195 -----
195 -----
196 You will see from the code that we had to further decorate the
196 You will see from the code that we had to further decorate the
197 decorator with the nose.tools.make_decorator function in order to
197 decorator with the nose.tools.make_decorator function in order to
198 transmit function name, and various other metadata.
198 transmit function name, and various other metadata.
199 '''
199 '''
200
200
201 def skip_decorator(f):
201 def skip_decorator(f):
202 # Local import to avoid a hard nose dependency and only incur the
202 # Local import to avoid a hard nose dependency and only incur the
203 # import time overhead at actual test-time.
203 # import time overhead at actual test-time.
204 import nose
204 import nose
205
205
206 # Allow for both boolean or callable skip conditions.
206 # Allow for both boolean or callable skip conditions.
207 if callable(skip_condition):
207 if callable(skip_condition):
208 skip_val = skip_condition
208 skip_val = skip_condition
209 else:
209 else:
210 skip_val = lambda : skip_condition
210 skip_val = lambda : skip_condition
211
211
212 def get_msg(func,msg=None):
212 def get_msg(func,msg=None):
213 """Skip message with information about function being skipped."""
213 """Skip message with information about function being skipped."""
214 if msg is None: out = 'Test skipped due to test condition.'
214 if msg is None: out = 'Test skipped due to test condition.'
215 else: out = msg
215 else: out = msg
216 return "Skipping test: %s. %s" % (func.__name__,out)
216 return "Skipping test: %s. %s" % (func.__name__,out)
217
217
218 # We need to define *two* skippers because Python doesn't allow both
218 # We need to define *two* skippers because Python doesn't allow both
219 # return with value and yield inside the same function.
219 # return with value and yield inside the same function.
220 def skipper_func(*args, **kwargs):
220 def skipper_func(*args, **kwargs):
221 """Skipper for normal test functions."""
221 """Skipper for normal test functions."""
222 if skip_val():
222 if skip_val():
223 raise nose.SkipTest(get_msg(f,msg))
223 raise nose.SkipTest(get_msg(f,msg))
224 else:
224 else:
225 return f(*args, **kwargs)
225 return f(*args, **kwargs)
226
226
227 def skipper_gen(*args, **kwargs):
227 def skipper_gen(*args, **kwargs):
228 """Skipper for test generators."""
228 """Skipper for test generators."""
229 if skip_val():
229 if skip_val():
230 raise nose.SkipTest(get_msg(f,msg))
230 raise nose.SkipTest(get_msg(f,msg))
231 else:
231 else:
232 for x in f(*args, **kwargs):
232 for x in f(*args, **kwargs):
233 yield x
233 yield x
234
234
235 # Choose the right skipper to use when building the actual generator.
235 # Choose the right skipper to use when building the actual generator.
236 if nose.util.isgenerator(f):
236 if nose.util.isgenerator(f):
237 skipper = skipper_gen
237 skipper = skipper_gen
238 else:
238 else:
239 skipper = skipper_func
239 skipper = skipper_func
240
240
241 return nose.tools.make_decorator(f)(skipper)
241 return nose.tools.make_decorator(f)(skipper)
242
242
243 return skip_decorator
243 return skip_decorator
244
244
245 # A version with the condition set to true, common case just to attacha message
245 # A version with the condition set to true, common case just to attacha message
246 # to a skip decorator
246 # to a skip decorator
247 def skip(msg=None):
247 def skip(msg=None):
248 """Decorator factory - mark a test function for skipping from test suite.
248 """Decorator factory - mark a test function for skipping from test suite.
249
249
250 Parameters
250 Parameters
251 ----------
251 ----------
252 msg : string
252 msg : string
253 Optional message to be added.
253 Optional message to be added.
254
254
255 Returns
255 Returns
256 -------
256 -------
257 decorator : function
257 decorator : function
258 Decorator, which, when applied to a function, causes SkipTest
258 Decorator, which, when applied to a function, causes SkipTest
259 to be raised, with the optional message added.
259 to be raised, with the optional message added.
260 """
260 """
261
261
262 return skipif(True,msg)
262 return skipif(True,msg)
263
263
264
264
265 def onlyif(condition, msg):
265 def onlyif(condition, msg):
266 """The reverse from skipif, see skipif for details."""
266 """The reverse from skipif, see skipif for details."""
267
267
268 if callable(condition):
268 if callable(condition):
269 skip_condition = lambda : not condition()
269 skip_condition = lambda : not condition()
270 else:
270 else:
271 skip_condition = lambda : not condition
271 skip_condition = lambda : not condition
272
272
273 return skipif(skip_condition, msg)
273 return skipif(skip_condition, msg)
274
274
275 #-----------------------------------------------------------------------------
275 #-----------------------------------------------------------------------------
276 # Utility functions for decorators
276 # Utility functions for decorators
277 def numpy_not_available():
277 def numpy_not_available():
278 """Can numpy be imported? Returns true if numpy does NOT import.
278 """Can numpy be imported? Returns true if numpy does NOT import.
279
279
280 This is used to make a decorator to skip tests that require numpy to be
280 This is used to make a decorator to skip tests that require numpy to be
281 available, but delay the 'import numpy' to test execution time.
281 available, but delay the 'import numpy' to test execution time.
282 """
282 """
283 try:
283 try:
284 import numpy
284 import numpy
285 np_not_avail = False
285 np_not_avail = False
286 except ImportError:
286 except ImportError:
287 np_not_avail = True
287 np_not_avail = True
288
288
289 return np_not_avail
289 return np_not_avail
290
290
291 #-----------------------------------------------------------------------------
291 #-----------------------------------------------------------------------------
292 # Decorators for public use
292 # Decorators for public use
293
293
294 skip_doctest = make_label_dec('skip_doctest',
294 skip_doctest = make_label_dec('skip_doctest',
295 """Decorator - mark a function or method for skipping its doctest.
295 """Decorator - mark a function or method for skipping its doctest.
296
296
297 This decorator allows you to mark a function whose docstring you wish to
297 This decorator allows you to mark a function whose docstring you wish to
298 omit from testing, while preserving the docstring for introspection, help,
298 omit from testing, while preserving the docstring for introspection, help,
299 etc.""")
299 etc.""")
300
300
301 # Decorators to skip certain tests on specific platforms.
301 # Decorators to skip certain tests on specific platforms.
302 skip_win32 = skipif(sys.platform == 'win32',
302 skip_win32 = skipif(sys.platform == 'win32',
303 "This test does not run under Windows")
303 "This test does not run under Windows")
304 skip_linux = skipif(sys.platform == 'linux2',
304 skip_linux = skipif(sys.platform == 'linux2',
305 "This test does not run under Linux")
305 "This test does not run under Linux")
306 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
306 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
307
307
308
308
309 # Decorators to skip tests if not on specific platforms.
309 # Decorators to skip tests if not on specific platforms.
310 skip_if_not_win32 = skipif(sys.platform != 'win32',
310 skip_if_not_win32 = skipif(sys.platform != 'win32',
311 "This test only runs under Windows")
311 "This test only runs under Windows")
312 skip_if_not_linux = skipif(sys.platform != 'linux2',
312 skip_if_not_linux = skipif(sys.platform != 'linux2',
313 "This test only runs under Linux")
313 "This test only runs under Linux")
314 skip_if_not_osx = skipif(sys.platform != 'darwin',
314 skip_if_not_osx = skipif(sys.platform != 'darwin',
315 "This test only runs under OSX")
315 "This test only runs under OSX")
316
316
317 # Other skip decorators
317 # Other skip decorators
318 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
318 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
319
319
320 skipknownfailure = skip('This test is known to fail')
320 skipknownfailure = skip('This test is known to fail')
321
322 # A null 'decorator', useful to make more readable code that needs to pick
323 # between different decorators based on OS or other conditions
324 null_deco = lambda f: f
@@ -1,408 +1,416 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 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Module imports
22 # Module imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 # Stdlib
25 # Stdlib
26 import os
26 import os
27 import os.path as path
27 import os.path as path
28 import signal
28 import signal
29 import sys
29 import sys
30 import subprocess
30 import subprocess
31 import tempfile
31 import tempfile
32 import time
32 import time
33 import warnings
33 import warnings
34
34
35 # Note: monkeypatch!
35 # Note: monkeypatch!
36 # We need to monkeypatch a small problem in nose itself first, before importing
36 # We need to monkeypatch a small problem in nose itself first, before importing
37 # it for actual use. This should get into nose upstream, but its release cycle
37 # it for actual use. This should get into nose upstream, but its release cycle
38 # is slow and we need it for our parametric tests to work correctly.
38 # is slow and we need it for our parametric tests to work correctly.
39 from . import nosepatch
39 from . import nosepatch
40 # Now, proceed to import nose itself
40 # Now, proceed to import nose itself
41 import nose.plugins.builtin
41 import nose.plugins.builtin
42 from nose.core import TestProgram
42 from nose.core import TestProgram
43
43
44 # Our own imports
44 # Our own imports
45 from IPython.utils import genutils
45 from IPython.utils import genutils
46 from IPython.utils.platutils import find_cmd, FindCmdError
46 from IPython.utils.platutils import find_cmd, FindCmdError
47 from . import globalipapp
47 from . import globalipapp
48 from . import tools
48 from .plugin.ipdoctest import IPythonDoctest
49 from .plugin.ipdoctest import IPythonDoctest
49
50
50 pjoin = path.join
51 pjoin = path.join
51
52
52 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
53 # Warnings control
54 # Warnings control
54 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
55 # Twisted generates annoying warnings with Python 2.6, as will do other code
56 # Twisted generates annoying warnings with Python 2.6, as will do other code
56 # that imports 'sets' as of today
57 # that imports 'sets' as of today
57 warnings.filterwarnings('ignore', 'the sets module is deprecated',
58 warnings.filterwarnings('ignore', 'the sets module is deprecated',
58 DeprecationWarning )
59 DeprecationWarning )
59
60
61 # This one also comes from Twisted
62 warnings.filterwarnings('ignore', 'the sha module is deprecated',
63 DeprecationWarning)
64
60 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
61 # Logic for skipping doctests
66 # Logic for skipping doctests
62 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
63
68
64 def test_for(mod):
69 def test_for(mod):
65 """Test to see if mod is importable."""
70 """Test to see if mod is importable."""
66 try:
71 try:
67 __import__(mod)
72 __import__(mod)
68 except ImportError:
73 except ImportError:
69 return False
74 return False
70 else:
75 else:
71 return True
76 return True
72
77
73
78
74 have_curses = test_for('_curses')
79 have_curses = test_for('_curses')
75 have_wx = test_for('wx')
80 have_wx = test_for('wx')
76 have_wx_aui = test_for('wx.aui')
81 have_wx_aui = test_for('wx.aui')
77 have_zi = test_for('zope.interface')
82 have_zi = test_for('zope.interface')
78 have_twisted = test_for('twisted')
83 have_twisted = test_for('twisted')
79 have_foolscap = test_for('foolscap')
84 have_foolscap = test_for('foolscap')
80 have_objc = test_for('objc')
85 have_objc = test_for('objc')
81 have_pexpect = test_for('pexpect')
86 have_pexpect = test_for('pexpect')
82 have_gtk = test_for('gtk')
87 have_gtk = test_for('gtk')
83 have_gobject = test_for('gobject')
88 have_gobject = test_for('gobject')
84
89
85
90
86 def make_exclude():
91 def make_exclude():
87 """Make patterns of modules and packages to exclude from testing.
92 """Make patterns of modules and packages to exclude from testing.
88
93
89 For the IPythonDoctest plugin, we need to exclude certain patterns that
94 For the IPythonDoctest plugin, we need to exclude certain patterns that
90 cause testing problems. We should strive to minimize the number of
95 cause testing problems. We should strive to minimize the number of
91 skipped modules, since this means untested code. As the testing
96 skipped modules, since this means untested code. As the testing
92 machinery solidifies, this list should eventually become empty.
97 machinery solidifies, this list should eventually become empty.
93 These modules and packages will NOT get scanned by nose at all for tests.
98 These modules and packages will NOT get scanned by nose at all for tests.
94 """
99 """
95 # Simple utility to make IPython paths more readably, we need a lot of
100 # Simple utility to make IPython paths more readably, we need a lot of
96 # these below
101 # these below
97 ipjoin = lambda *paths: pjoin('IPython', *paths)
102 ipjoin = lambda *paths: pjoin('IPython', *paths)
98
103
99 exclusions = [ipjoin('external'),
104 exclusions = [ipjoin('external'),
100 ipjoin('frontend', 'process', 'winprocess.py'),
105 ipjoin('frontend', 'process', 'winprocess.py'),
101 pjoin('IPython_doctest_plugin'),
106 pjoin('IPython_doctest_plugin'),
102 ipjoin('quarantine'),
107 ipjoin('quarantine'),
103 ipjoin('deathrow'),
108 ipjoin('deathrow'),
104 ipjoin('testing', 'attic'),
109 ipjoin('testing', 'attic'),
105 ipjoin('testing', 'tools'),
110 # This guy is probably attic material
106 ipjoin('testing', 'mkdoctests'),
111 ipjoin('testing', 'mkdoctests'),
112 # Testing inputhook will need a lot of thought, to figure out
113 # how to have tests that don't lock up with the gui event
114 # loops in the picture
107 ipjoin('lib', 'inputhook'),
115 ipjoin('lib', 'inputhook'),
108 # Config files aren't really importable stand-alone
116 # Config files aren't really importable stand-alone
109 ipjoin('config', 'default'),
117 ipjoin('config', 'default'),
110 ipjoin('config', 'profile'),
118 ipjoin('config', 'profile'),
111 ]
119 ]
112
120
113 if not have_wx:
121 if not have_wx:
114 exclusions.append(ipjoin('gui'))
122 exclusions.append(ipjoin('gui'))
115 exclusions.append(ipjoin('frontend', 'wx'))
123 exclusions.append(ipjoin('frontend', 'wx'))
116 exclusions.append(ipjoin('lib', 'inputhookwx'))
124 exclusions.append(ipjoin('lib', 'inputhookwx'))
117
125
118 if not have_gtk or not have_gobject:
126 if not have_gtk or not have_gobject:
119 exclusions.append(ipjoin('lib', 'inputhookgtk'))
127 exclusions.append(ipjoin('lib', 'inputhookgtk'))
120
128
121 if not have_wx_aui:
129 if not have_wx_aui:
122 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
130 exclusions.append(ipjoin('gui', 'wx', 'wxIPython'))
123
131
124 if not have_objc:
132 if not have_objc:
125 exclusions.append(ipjoin('frontend', 'cocoa'))
133 exclusions.append(ipjoin('frontend', 'cocoa'))
126
134
127 if not sys.platform == 'win32':
135 if not sys.platform == 'win32':
128 exclusions.append(ipjoin('utils', 'platutils_win32'))
136 exclusions.append(ipjoin('utils', 'platutils_win32'))
129
137
130 # These have to be skipped on win32 because the use echo, rm, cd, etc.
138 # These have to be skipped on win32 because the use echo, rm, cd, etc.
131 # See ticket https://bugs.launchpad.net/bugs/366982
139 # See ticket https://bugs.launchpad.net/bugs/366982
132 if sys.platform == 'win32':
140 if sys.platform == 'win32':
133 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
141 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
134 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
142 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
135
143
136 if not os.name == 'posix':
144 if not os.name == 'posix':
137 exclusions.append(ipjoin('utils', 'platutils_posix'))
145 exclusions.append(ipjoin('utils', 'platutils_posix'))
138
146
139 if not have_pexpect:
147 if not have_pexpect:
140 exclusions.extend([ipjoin('scripts', 'irunner'),
148 exclusions.extend([ipjoin('scripts', 'irunner'),
141 ipjoin('lib', 'irunner')])
149 ipjoin('lib', 'irunner')])
142
150
143 # This is scary. We still have things in frontend and testing that
151 # This is scary. We still have things in frontend and testing that
144 # are being tested by nose that use twisted. We need to rethink
152 # are being tested by nose that use twisted. We need to rethink
145 # how we are isolating dependencies in testing.
153 # how we are isolating dependencies in testing.
146 if not (have_twisted and have_zi and have_foolscap):
154 if not (have_twisted and have_zi and have_foolscap):
147 exclusions.extend(
155 exclusions.extend(
148 [ipjoin('frontend', 'asyncfrontendbase'),
156 [ipjoin('frontend', 'asyncfrontendbase'),
149 ipjoin('frontend', 'prefilterfrontend'),
157 ipjoin('frontend', 'prefilterfrontend'),
150 ipjoin('frontend', 'frontendbase'),
158 ipjoin('frontend', 'frontendbase'),
151 ipjoin('frontend', 'linefrontendbase'),
159 ipjoin('frontend', 'linefrontendbase'),
152 ipjoin('frontend', 'tests', 'test_linefrontend'),
160 ipjoin('frontend', 'tests', 'test_linefrontend'),
153 ipjoin('frontend', 'tests', 'test_frontendbase'),
161 ipjoin('frontend', 'tests', 'test_frontendbase'),
154 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
162 ipjoin('frontend', 'tests', 'test_prefilterfrontend'),
155 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
163 ipjoin('frontend', 'tests', 'test_asyncfrontendbase'),
156 ipjoin('testing', 'parametric'),
164 ipjoin('testing', 'parametric'),
157 ipjoin('testing', 'util'),
165 ipjoin('testing', 'util'),
158 ipjoin('testing', 'tests', 'test_decorators_trial'),
166 ipjoin('testing', 'tests', 'test_decorators_trial'),
159 ] )
167 ] )
160
168
161 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
169 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
162 if sys.platform == 'win32':
170 if sys.platform == 'win32':
163 exclusions = [s.replace('\\','\\\\') for s in exclusions]
171 exclusions = [s.replace('\\','\\\\') for s in exclusions]
164
172
165 return exclusions
173 return exclusions
166
174
167
175
168 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
169 # Functions and classes
177 # Functions and classes
170 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
171
179
172 class IPTester(object):
180 class IPTester(object):
173 """Call that calls iptest or trial in a subprocess.
181 """Call that calls iptest or trial in a subprocess.
174 """
182 """
175 #: string, name of test runner that will be called
183 #: string, name of test runner that will be called
176 runner = None
184 runner = None
177 #: list, parameters for test runner
185 #: list, parameters for test runner
178 params = None
186 params = None
179 #: list, arguments of system call to be made to call test runner
187 #: list, arguments of system call to be made to call test runner
180 call_args = None
188 call_args = None
181 #: list, process ids of subprocesses we start (for cleanup)
189 #: list, process ids of subprocesses we start (for cleanup)
182 pids = None
190 pids = None
183
191
184 def __init__(self,runner='iptest',params=None):
192 def __init__(self,runner='iptest',params=None):
185 """Create new test runner."""
193 """Create new test runner."""
186 if runner == 'iptest':
194 if runner == 'iptest':
187 # Find our own 'iptest' script OS-level entry point
195 # Find our own 'iptest' script OS-level entry point
188 try:
196 try:
189 iptest_path = os.path.abspath(find_cmd('iptest'))
197 iptest_path = os.path.abspath(find_cmd('iptest'))
190 except FindCmdError:
198 except FindCmdError:
191 # Script not installed (may be the case for testing situations
199 # Script not installed (may be the case for testing situations
192 # that are running from a source tree only), pull from internal
200 # that are running from a source tree only), pull from internal
193 # path:
201 # path:
194 iptest_path = pjoin(genutils.get_ipython_package_dir(),
202 iptest_path = pjoin(genutils.get_ipython_package_dir(),
195 'scripts','iptest')
203 'scripts','iptest')
196 self.runner = ['python', iptest_path, '-v']
204 self.runner = tools.cmd2argv(iptest_path) + ['-v']
197 else:
205 else:
198 self.runner = ['python', os.path.abspath(find_cmd('trial'))]
206 self.runner = tools.cmd2argv(os.path.abspath(find_cmd('trial')))
199 if params is None:
207 if params is None:
200 params = []
208 params = []
201 if isinstance(params,str):
209 if isinstance(params,str):
202 params = [params]
210 params = [params]
203 self.params = params
211 self.params = params
204
212
205 # Assemble call
213 # Assemble call
206 self.call_args = self.runner+self.params
214 self.call_args = self.runner+self.params
207
215
208 # Store pids of anything we start to clean up on deletion, if possible
216 # Store pids of anything we start to clean up on deletion, if possible
209 # (on posix only, since win32 has no os.kill)
217 # (on posix only, since win32 has no os.kill)
210 self.pids = []
218 self.pids = []
211
219
212 if sys.platform == 'win32':
220 if sys.platform == 'win32':
213 def _run_cmd(self):
221 def _run_cmd(self):
214 # On Windows, use os.system instead of subprocess.call, because I
222 # On Windows, use os.system instead of subprocess.call, because I
215 # was having problems with subprocess and I just don't know enough
223 # was having problems with subprocess and I just don't know enough
216 # about win32 to debug this reliably. Os.system may be the 'old
224 # about win32 to debug this reliably. Os.system may be the 'old
217 # fashioned' way to do it, but it works just fine. If someone
225 # fashioned' way to do it, but it works just fine. If someone
218 # later can clean this up that's fine, as long as the tests run
226 # later can clean this up that's fine, as long as the tests run
219 # reliably in win32.
227 # reliably in win32.
220 return os.system(' '.join(self.call_args))
228 return os.system(' '.join(self.call_args))
221 else:
229 else:
222 def _run_cmd(self):
230 def _run_cmd(self):
223 subp = subprocess.Popen(self.call_args)
231 subp = subprocess.Popen(self.call_args)
224 self.pids.append(subp.pid)
232 self.pids.append(subp.pid)
225 # If this fails, the pid will be left in self.pids and cleaned up
233 # If this fails, the pid will be left in self.pids and cleaned up
226 # later, but if the wait call succeeds, then we can clear the
234 # later, but if the wait call succeeds, then we can clear the
227 # stored pid.
235 # stored pid.
228 retcode = subp.wait()
236 retcode = subp.wait()
229 self.pids.pop()
237 self.pids.pop()
230 return retcode
238 return retcode
231
239
232 def run(self):
240 def run(self):
233 """Run the stored commands"""
241 """Run the stored commands"""
234 try:
242 try:
235 return self._run_cmd()
243 return self._run_cmd()
236 except:
244 except:
237 import traceback
245 import traceback
238 traceback.print_exc()
246 traceback.print_exc()
239 return 1 # signal failure
247 return 1 # signal failure
240
248
241 def __del__(self):
249 def __del__(self):
242 """Cleanup on exit by killing any leftover processes."""
250 """Cleanup on exit by killing any leftover processes."""
243
251
244 if not hasattr(os, 'kill'):
252 if not hasattr(os, 'kill'):
245 return
253 return
246
254
247 for pid in self.pids:
255 for pid in self.pids:
248 try:
256 try:
249 print 'Cleaning stale PID:', pid
257 print 'Cleaning stale PID:', pid
250 os.kill(pid, signal.SIGKILL)
258 os.kill(pid, signal.SIGKILL)
251 except OSError:
259 except OSError:
252 # This is just a best effort, if we fail or the process was
260 # This is just a best effort, if we fail or the process was
253 # really gone, ignore it.
261 # really gone, ignore it.
254 pass
262 pass
255
263
256
264
257 def make_runners():
265 def make_runners():
258 """Define the top-level packages that need to be tested.
266 """Define the top-level packages that need to be tested.
259 """
267 """
260
268
261 nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib',
269 nose_packages = ['config', 'core', 'extensions', 'frontend', 'lib',
262 'scripts', 'testing', 'utils']
270 'scripts', 'testing', 'utils']
263 trial_packages = ['kernel']
271 trial_packages = ['kernel']
264
272
265 if have_wx:
273 if have_wx:
266 nose_packages.append('gui')
274 nose_packages.append('gui')
267
275
268 #nose_packages = ['core'] # dbg
276 #nose_packages = ['core'] # dbg
269 #trial_packages = [] # dbg
277 #trial_packages = [] # dbg
270
278
271 nose_packages = ['IPython.%s' % m for m in nose_packages ]
279 nose_packages = ['IPython.%s' % m for m in nose_packages ]
272 trial_packages = ['IPython.%s' % m for m in trial_packages ]
280 trial_packages = ['IPython.%s' % m for m in trial_packages ]
273
281
274 # Make runners, most with nose
282 # Make runners, most with nose
275 nose_testers = [IPTester(params=v) for v in nose_packages]
283 nose_testers = [IPTester(params=v) for v in nose_packages]
276 runners = dict(zip(nose_packages, nose_testers))
284 runners = dict(zip(nose_packages, nose_testers))
277 # And add twisted ones if conditions are met
285 # And add twisted ones if conditions are met
278 if have_zi and have_twisted and have_foolscap:
286 if have_zi and have_twisted and have_foolscap:
279 trial_testers = [IPTester('trial',params=v) for v in trial_packages]
287 trial_testers = [IPTester('trial',params=v) for v in trial_packages]
280 runners.update(dict(zip(trial_packages,trial_testers)))
288 runners.update(dict(zip(trial_packages,trial_testers)))
281
289
282 return runners
290 return runners
283
291
284
292
285 def run_iptest():
293 def run_iptest():
286 """Run the IPython test suite using nose.
294 """Run the IPython test suite using nose.
287
295
288 This function is called when this script is **not** called with the form
296 This function is called when this script is **not** called with the form
289 `iptest all`. It simply calls nose with appropriate command line flags
297 `iptest all`. It simply calls nose with appropriate command line flags
290 and accepts all of the standard nose arguments.
298 and accepts all of the standard nose arguments.
291 """
299 """
292
300
293 warnings.filterwarnings('ignore',
301 warnings.filterwarnings('ignore',
294 'This will be removed soon. Use IPython.testing.util instead')
302 'This will be removed soon. Use IPython.testing.util instead')
295
303
296 argv = sys.argv + [ '--detailed-errors',
304 argv = sys.argv + [ '--detailed-errors',
297 # Loading ipdoctest causes problems with Twisted, but
305 # Loading ipdoctest causes problems with Twisted, but
298 # our test suite runner now separates things and runs
306 # our test suite runner now separates things and runs
299 # all Twisted tests with trial.
307 # all Twisted tests with trial.
300 '--with-ipdoctest',
308 '--with-ipdoctest',
301 '--ipdoctest-tests','--ipdoctest-extension=txt',
309 '--ipdoctest-tests','--ipdoctest-extension=txt',
302
310
303 #'-x','-s', # dbg
311 #'-x','-s', # dbg
304
312
305 # We add --exe because of setuptools' imbecility (it
313 # We add --exe because of setuptools' imbecility (it
306 # blindly does chmod +x on ALL files). Nose does the
314 # blindly does chmod +x on ALL files). Nose does the
307 # right thing and it tries to avoid executables,
315 # right thing and it tries to avoid executables,
308 # setuptools unfortunately forces our hand here. This
316 # setuptools unfortunately forces our hand here. This
309 # has been discussed on the distutils list and the
317 # has been discussed on the distutils list and the
310 # setuptools devs refuse to fix this problem!
318 # setuptools devs refuse to fix this problem!
311 '--exe',
319 '--exe',
312 ]
320 ]
313
321
314 # Detect if any tests were required by explicitly calling an IPython
322 # Detect if any tests were required by explicitly calling an IPython
315 # submodule or giving a specific path
323 # submodule or giving a specific path
316 has_tests = False
324 has_tests = False
317 for arg in sys.argv:
325 for arg in sys.argv:
318 if 'IPython' in arg or arg.endswith('.py') or \
326 if 'IPython' in arg or arg.endswith('.py') or \
319 (':' in arg and '.py' in arg):
327 (':' in arg and '.py' in arg):
320 has_tests = True
328 has_tests = True
321 break
329 break
322
330
323 # If nothing was specifically requested, test full IPython
331 # If nothing was specifically requested, test full IPython
324 if not has_tests:
332 if not has_tests:
325 argv.append('IPython')
333 argv.append('IPython')
326
334
327 ## # Construct list of plugins, omitting the existing doctest plugin, which
335 ## # Construct list of plugins, omitting the existing doctest plugin, which
328 ## # ours replaces (and extends).
336 ## # ours replaces (and extends).
329 plugins = [IPythonDoctest(make_exclude())]
337 plugins = [IPythonDoctest(make_exclude())]
330 for p in nose.plugins.builtin.plugins:
338 for p in nose.plugins.builtin.plugins:
331 plug = p()
339 plug = p()
332 if plug.name == 'doctest':
340 if plug.name == 'doctest':
333 continue
341 continue
334 plugins.append(plug)
342 plugins.append(plug)
335
343
336 # We need a global ipython running in this process
344 # We need a global ipython running in this process
337 globalipapp.start_ipython()
345 globalipapp.start_ipython()
338 # Now nose can run
346 # Now nose can run
339 TestProgram(argv=argv,plugins=plugins)
347 TestProgram(argv=argv,plugins=plugins)
340
348
341
349
342 def run_iptestall():
350 def run_iptestall():
343 """Run the entire IPython test suite by calling nose and trial.
351 """Run the entire IPython test suite by calling nose and trial.
344
352
345 This function constructs :class:`IPTester` instances for all IPython
353 This function constructs :class:`IPTester` instances for all IPython
346 modules and package and then runs each of them. This causes the modules
354 modules and package and then runs each of them. This causes the modules
347 and packages of IPython to be tested each in their own subprocess using
355 and packages of IPython to be tested each in their own subprocess using
348 nose or twisted.trial appropriately.
356 nose or twisted.trial appropriately.
349 """
357 """
350
358
351 runners = make_runners()
359 runners = make_runners()
352
360
353 # Run the test runners in a temporary dir so we can nuke it when finished
361 # Run the test runners in a temporary dir so we can nuke it when finished
354 # to clean up any junk files left over by accident. This also makes it
362 # to clean up any junk files left over by accident. This also makes it
355 # robust against being run in non-writeable directories by mistake, as the
363 # robust against being run in non-writeable directories by mistake, as the
356 # temp dir will always be user-writeable.
364 # temp dir will always be user-writeable.
357 curdir = os.getcwd()
365 curdir = os.getcwd()
358 testdir = tempfile.gettempdir()
366 testdir = tempfile.gettempdir()
359 os.chdir(testdir)
367 os.chdir(testdir)
360
368
361 # Run all test runners, tracking execution time
369 # Run all test runners, tracking execution time
362 failed = {}
370 failed = {}
363 t_start = time.time()
371 t_start = time.time()
364 try:
372 try:
365 for name,runner in runners.iteritems():
373 for name,runner in runners.iteritems():
366 print '*'*77
374 print '*'*77
367 print 'IPython test group:',name
375 print 'IPython test group:',name
368 res = runner.run()
376 res = runner.run()
369 if res:
377 if res:
370 failed[name] = res
378 failed[name] = res
371 finally:
379 finally:
372 os.chdir(curdir)
380 os.chdir(curdir)
373 t_end = time.time()
381 t_end = time.time()
374 t_tests = t_end - t_start
382 t_tests = t_end - t_start
375 nrunners = len(runners)
383 nrunners = len(runners)
376 nfail = len(failed)
384 nfail = len(failed)
377 # summarize results
385 # summarize results
378 print
386 print
379 print '*'*77
387 print '*'*77
380 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
388 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
381 print
389 print
382 if not failed:
390 if not failed:
383 print 'OK'
391 print 'OK'
384 else:
392 else:
385 # If anything went wrong, point out what command to rerun manually to
393 # If anything went wrong, point out what command to rerun manually to
386 # see the actual errors and individual summary
394 # see the actual errors and individual summary
387 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
395 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
388 for name in failed:
396 for name in failed:
389 failed_runner = runners[name]
397 failed_runner = runners[name]
390 print '-'*40
398 print '-'*40
391 print 'Runner failed:',name
399 print 'Runner failed:',name
392 print 'You may wish to rerun this one individually, with:'
400 print 'You may wish to rerun this one individually, with:'
393 print ' '.join(failed_runner.call_args)
401 print ' '.join(failed_runner.call_args)
394 print
402 print
395
403
396
404
397 def main():
405 def main():
398 if len(sys.argv) == 1:
406 if len(sys.argv) == 1:
399 run_iptestall()
407 run_iptestall()
400 else:
408 else:
401 if sys.argv[1] == 'all':
409 if sys.argv[1] == 'all':
402 run_iptestall()
410 run_iptestall()
403 else:
411 else:
404 run_iptest()
412 run_iptest()
405
413
406
414
407 if __name__ == '__main__':
415 if __name__ == '__main__':
408 main()
416 main()
@@ -1,189 +1,189 b''
1 """Experimental code for cleaner support of IPython syntax with unittest.
1 """Experimental code for cleaner support of IPython syntax with unittest.
2
2
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
4 tests with IPython special syntax, and this has proved to be extremely slow.
4 tests with IPython special syntax, and this has proved to be extremely slow.
5 This module provides decorators to try a different approach, stemming from a
5 This module provides decorators to try a different approach, stemming from a
6 conversation Brian and I (FP) had about this problem Sept/09.
6 conversation Brian and I (FP) had about this problem Sept/09.
7
7
8 The goal is to be able to easily write simple functions that can be seen by
8 The goal is to be able to easily write simple functions that can be seen by
9 unittest as tests, and ultimately for these to support doctests with full
9 unittest as tests, and ultimately for these to support doctests with full
10 IPython syntax. Nose already offers this based on naming conventions and our
10 IPython syntax. Nose already offers this based on naming conventions and our
11 hackish plugins, but we are seeking to move away from nose dependencies if
11 hackish plugins, but we are seeking to move away from nose dependencies if
12 possible.
12 possible.
13
13
14 This module follows a different approach, based on decorators.
14 This module follows a different approach, based on decorators.
15
15
16 - A decorator called @ipdoctest can mark any function as having a docstring
16 - A decorator called @ipdoctest can mark any function as having a docstring
17 that should be viewed as a doctest, but after syntax conversion.
17 that should be viewed as a doctest, but after syntax conversion.
18
18
19 Authors
19 Authors
20 -------
20 -------
21
21
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 """
23 """
24
24
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Copyright (C) 2009 The IPython Development Team
28 # Copyright (C) 2009 The IPython Development Team
29 #
29 #
30 # Distributed under the terms of the BSD License. The full license is in
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
31 # the file COPYING, distributed as part of this software.
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Imports
36 # Imports
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 # Stdlib
39 # Stdlib
40 import re
40 import re
41 import sys
41 import sys
42 import unittest
42 import unittest
43 from doctest import DocTestFinder, DocTestRunner
43 from doctest import DocTestFinder, DocTestRunner
44 try:
44 try:
45 from doctest import TestResults
45 from doctest import TestResults
46 except:
46 except:
47 from ._doctest26 import TestResults
47 from ._doctest26 import TestResults
48
48
49 # We already have python3-compliant code for parametric tests
49 # We already have python3-compliant code for parametric tests
50 if sys.version[0]=='2':
50 if sys.version[0]=='2':
51 from ._paramtestpy2 import ParametricTestCase
51 from ._paramtestpy2 import ParametricTestCase
52 else:
52 else:
53 from ._paramtestpy3 import ParametricTestCase
53 from ._paramtestpy3 import ParametricTestCase
54
54
55 from . import globalipapp
56
57 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
58 # Classes and functions
56 # Classes and functions
59 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
60
58
61 def count_failures(runner):
59 def count_failures(runner):
62 """Count number of failures in a doctest runner.
60 """Count number of failures in a doctest runner.
63
61
64 Code modeled after the summarize() method in doctest.
62 Code modeled after the summarize() method in doctest.
65 """
63 """
66 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
64 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
67
65
68
66
69 class IPython2PythonConverter(object):
67 class IPython2PythonConverter(object):
70 """Convert IPython 'syntax' to valid Python.
68 """Convert IPython 'syntax' to valid Python.
71
69
72 Eventually this code may grow to be the full IPython syntax conversion
70 Eventually this code may grow to be the full IPython syntax conversion
73 implementation, but for now it only does prompt convertion."""
71 implementation, but for now it only does prompt convertion."""
74
72
75 def __init__(self):
73 def __init__(self):
76 self.rps1 = re.compile(r'In\ \[\d+\]: ')
74 self.rps1 = re.compile(r'In\ \[\d+\]: ')
77 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
75 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
78 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
76 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
79 self.pyps1 = '>>> '
77 self.pyps1 = '>>> '
80 self.pyps2 = '... '
78 self.pyps2 = '... '
81 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
79 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
82 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
80 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
83
81
84 def __call__(self, ds):
82 def __call__(self, ds):
85 """Convert IPython prompts to python ones in a string."""
83 """Convert IPython prompts to python ones in a string."""
84 from . import globalipapp
85
86 pyps1 = '>>> '
86 pyps1 = '>>> '
87 pyps2 = '... '
87 pyps2 = '... '
88 pyout = ''
88 pyout = ''
89
89
90 dnew = ds
90 dnew = ds
91 dnew = self.rps1.sub(pyps1, dnew)
91 dnew = self.rps1.sub(pyps1, dnew)
92 dnew = self.rps2.sub(pyps2, dnew)
92 dnew = self.rps2.sub(pyps2, dnew)
93 dnew = self.rout.sub(pyout, dnew)
93 dnew = self.rout.sub(pyout, dnew)
94 ip = globalipapp.get_ipython()
94 ip = globalipapp.get_ipython()
95
95
96 # Convert input IPython source into valid Python.
96 # Convert input IPython source into valid Python.
97 out = []
97 out = []
98 newline = out.append
98 newline = out.append
99 for line in dnew.splitlines():
99 for line in dnew.splitlines():
100
100
101 mps1 = self.rpyps1.match(line)
101 mps1 = self.rpyps1.match(line)
102 if mps1 is not None:
102 if mps1 is not None:
103 prompt, text = mps1.groups()
103 prompt, text = mps1.groups()
104 newline(prompt+ip.prefilter(text, False))
104 newline(prompt+ip.prefilter(text, False))
105 continue
105 continue
106
106
107 mps2 = self.rpyps2.match(line)
107 mps2 = self.rpyps2.match(line)
108 if mps2 is not None:
108 if mps2 is not None:
109 prompt, text = mps2.groups()
109 prompt, text = mps2.groups()
110 newline(prompt+ip.prefilter(text, True))
110 newline(prompt+ip.prefilter(text, True))
111 continue
111 continue
112
112
113 newline(line)
113 newline(line)
114 newline('') # ensure a closing newline, needed by doctest
114 newline('') # ensure a closing newline, needed by doctest
115 #print "PYSRC:", '\n'.join(out) # dbg
115 #print "PYSRC:", '\n'.join(out) # dbg
116 return '\n'.join(out)
116 return '\n'.join(out)
117
117
118 #return dnew
118 #return dnew
119
119
120
120
121 class Doc2UnitTester(object):
121 class Doc2UnitTester(object):
122 """Class whose instances act as a decorator for docstring testing.
122 """Class whose instances act as a decorator for docstring testing.
123
123
124 In practice we're only likely to need one instance ever, made below (though
124 In practice we're only likely to need one instance ever, made below (though
125 no attempt is made at turning it into a singleton, there is no need for
125 no attempt is made at turning it into a singleton, there is no need for
126 that).
126 that).
127 """
127 """
128 def __init__(self, verbose=False):
128 def __init__(self, verbose=False):
129 """New decorator.
129 """New decorator.
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133
133
134 verbose : boolean, optional (False)
134 verbose : boolean, optional (False)
135 Passed to the doctest finder and runner to control verbosity.
135 Passed to the doctest finder and runner to control verbosity.
136 """
136 """
137 self.verbose = verbose
137 self.verbose = verbose
138 # We can reuse the same finder for all instances
138 # We can reuse the same finder for all instances
139 self.finder = DocTestFinder(verbose=verbose, recurse=False)
139 self.finder = DocTestFinder(verbose=verbose, recurse=False)
140
140
141 def __call__(self, func):
141 def __call__(self, func):
142 """Use as a decorator: doctest a function's docstring as a unittest.
142 """Use as a decorator: doctest a function's docstring as a unittest.
143
143
144 This version runs normal doctests, but the idea is to make it later run
144 This version runs normal doctests, but the idea is to make it later run
145 ipython syntax instead."""
145 ipython syntax instead."""
146
146
147 # Capture the enclosing instance with a different name, so the new
147 # Capture the enclosing instance with a different name, so the new
148 # class below can see it without confusion regarding its own 'self'
148 # class below can see it without confusion regarding its own 'self'
149 # that will point to the test instance at runtime
149 # that will point to the test instance at runtime
150 d2u = self
150 d2u = self
151
151
152 # Rewrite the function's docstring to have python syntax
152 # Rewrite the function's docstring to have python syntax
153 if func.__doc__ is not None:
153 if func.__doc__ is not None:
154 func.__doc__ = ip2py(func.__doc__)
154 func.__doc__ = ip2py(func.__doc__)
155
155
156 # Now, create a tester object that is a real unittest instance, so
156 # Now, create a tester object that is a real unittest instance, so
157 # normal unittest machinery (or Nose, or Trial) can find it.
157 # normal unittest machinery (or Nose, or Trial) can find it.
158 class Tester(unittest.TestCase):
158 class Tester(unittest.TestCase):
159 def test(self):
159 def test(self):
160 # Make a new runner per function to be tested
160 # Make a new runner per function to be tested
161 runner = DocTestRunner(verbose=d2u.verbose)
161 runner = DocTestRunner(verbose=d2u.verbose)
162 map(runner.run, d2u.finder.find(func, func.__name__))
162 map(runner.run, d2u.finder.find(func, func.__name__))
163 failed = count_failures(runner)
163 failed = count_failures(runner)
164 if failed:
164 if failed:
165 # Since we only looked at a single function's docstring,
165 # Since we only looked at a single function's docstring,
166 # failed should contain at most one item. More than that
166 # failed should contain at most one item. More than that
167 # is a case we can't handle and should error out on
167 # is a case we can't handle and should error out on
168 if len(failed) > 1:
168 if len(failed) > 1:
169 err = "Invalid number of test results:" % failed
169 err = "Invalid number of test results:" % failed
170 raise ValueError(err)
170 raise ValueError(err)
171 # Report a normal failure.
171 # Report a normal failure.
172 self.fail('failed doctests: %s' % str(failed[0]))
172 self.fail('failed doctests: %s' % str(failed[0]))
173
173
174 # Rename it so test reports have the original signature.
174 # Rename it so test reports have the original signature.
175 Tester.__name__ = func.__name__
175 Tester.__name__ = func.__name__
176 return Tester
176 return Tester
177
177
178
178
179 def ipdocstring(func):
179 def ipdocstring(func):
180 """Change the function docstring via ip2py.
180 """Change the function docstring via ip2py.
181 """
181 """
182 if func.__doc__ is not None:
182 if func.__doc__ is not None:
183 func.__doc__ = ip2py(func.__doc__)
183 func.__doc__ = ip2py(func.__doc__)
184 return func
184 return func
185
185
186
186
187 # Make an instance of the classes for public use
187 # Make an instance of the classes for public use
188 ipdoctest = Doc2UnitTester()
188 ipdoctest = Doc2UnitTester()
189 ip2py = IPython2PythonConverter()
189 ip2py = IPython2PythonConverter()
@@ -1,274 +1,315 b''
1 """Generic testing tools that do NOT depend on Twisted.
1 """Generic testing tools that do NOT depend on Twisted.
2
2
3 In particular, this module exposes a set of top-level assert* functions that
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
5 nose can not, at least as of nose 0.10.4).
6
6
7 Note: our testing package contains testing.util, which does depend on Twisted
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
10 instead.
11
11
12
12
13 Authors
13 Authors
14 -------
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
16 """
17
17
18 #*****************************************************************************
18 #*****************************************************************************
19 # Copyright (C) 2009 The IPython Development Team
19 # Copyright (C) 2009 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #*****************************************************************************
23 #*****************************************************************************
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Required modules and packages
26 # Required modules and packages
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 from __future__ import absolute_import
28
29
29 import os
30 import os
30 import re
31 import re
31 import sys
32 import sys
32 import tempfile
33 import tempfile
33
34
34 try:
35 try:
35 # These tools are used by parts of the runtime, so we make the nose
36 # These tools are used by parts of the runtime, so we make the nose
36 # dependency optional at this point. Nose is a hard dependency to run the
37 # dependency optional at this point. Nose is a hard dependency to run the
37 # test suite, but NOT to use ipython itself.
38 # test suite, but NOT to use ipython itself.
38 import nose.tools as nt
39 import nose.tools as nt
39 has_nose = True
40 has_nose = True
40 except ImportError:
41 except ImportError:
41 has_nose = False
42 has_nose = False
42
43
43 from IPython.utils import genutils, platutils
44 from IPython.utils import genutils, platutils
44
45
46 from . import decorators as dec
47
45 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
46 # Globals
49 # Globals
47 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
48
51
49 # Make a bunch of nose.tools assert wrappers that can be used in test
52 # Make a bunch of nose.tools assert wrappers that can be used in test
50 # generators. This will expose an assert* function for each one in nose.tools.
53 # generators. This will expose an assert* function for each one in nose.tools.
51
54
52 _tpl = """
55 _tpl = """
53 def %(name)s(*a,**kw):
56 def %(name)s(*a,**kw):
54 return nt.%(name)s(*a,**kw)
57 return nt.%(name)s(*a,**kw)
55 """
58 """
56
59
57 if has_nose:
60 if has_nose:
58 for _x in [a for a in dir(nt) if a.startswith('assert')]:
61 for _x in [a for a in dir(nt) if a.startswith('assert')]:
59 exec _tpl % dict(name=_x)
62 exec _tpl % dict(name=_x)
60
63
61 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
62 # Functions and classes
65 # Functions and classes
63 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
64
67
68 # The docstring for full_path doctests differently on win32 (different path
69 # separator) so just skip the doctest there. The example remains informative.
70 doctest_deco = dec.skip_doctest if sys.platform == 'win32' else dec.null_deco
65
71
72 @doctest_deco
66 def full_path(startPath,files):
73 def full_path(startPath,files):
67 """Make full paths for all the listed files, based on startPath.
74 """Make full paths for all the listed files, based on startPath.
68
75
69 Only the base part of startPath is kept, since this routine is typically
76 Only the base part of startPath is kept, since this routine is typically
70 used with a script's __file__ variable as startPath. The base of startPath
77 used with a script's __file__ variable as startPath. The base of startPath
71 is then prepended to all the listed files, forming the output list.
78 is then prepended to all the listed files, forming the output list.
72
79
73 Parameters
80 Parameters
74 ----------
81 ----------
75 startPath : string
82 startPath : string
76 Initial path to use as the base for the results. This path is split
83 Initial path to use as the base for the results. This path is split
77 using os.path.split() and only its first component is kept.
84 using os.path.split() and only its first component is kept.
78
85
79 files : string or list
86 files : string or list
80 One or more files.
87 One or more files.
81
88
82 Examples
89 Examples
83 --------
90 --------
84
91
85 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
92 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
86 ['/foo/a.txt', '/foo/b.txt']
93 ['/foo/a.txt', '/foo/b.txt']
87
94
88 >>> full_path('/foo',['a.txt','b.txt'])
95 >>> full_path('/foo',['a.txt','b.txt'])
89 ['/a.txt', '/b.txt']
96 ['/a.txt', '/b.txt']
90
97
91 If a single file is given, the output is still a list:
98 If a single file is given, the output is still a list:
92 >>> full_path('/foo','a.txt')
99 >>> full_path('/foo','a.txt')
93 ['/a.txt']
100 ['/a.txt']
94 """
101 """
95
102
96 files = genutils.list_strings(files)
103 files = genutils.list_strings(files)
97 base = os.path.split(startPath)[0]
104 base = os.path.split(startPath)[0]
98 return [ os.path.join(base,f) for f in files ]
105 return [ os.path.join(base,f) for f in files ]
99
106
100
107
101 def parse_test_output(txt):
108 def parse_test_output(txt):
102 """Parse the output of a test run and return errors, failures.
109 """Parse the output of a test run and return errors, failures.
103
110
104 Parameters
111 Parameters
105 ----------
112 ----------
106 txt : str
113 txt : str
107 Text output of a test run, assumed to contain a line of one of the
114 Text output of a test run, assumed to contain a line of one of the
108 following forms::
115 following forms::
109 'FAILED (errors=1)'
116 'FAILED (errors=1)'
110 'FAILED (failures=1)'
117 'FAILED (failures=1)'
111 'FAILED (errors=1, failures=1)'
118 'FAILED (errors=1, failures=1)'
112
119
113 Returns
120 Returns
114 -------
121 -------
115 nerr, nfail: number of errors and failures.
122 nerr, nfail: number of errors and failures.
116 """
123 """
117
124
118 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
125 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
119 if err_m:
126 if err_m:
120 nerr = int(err_m.group(1))
127 nerr = int(err_m.group(1))
121 nfail = 0
128 nfail = 0
122 return nerr, nfail
129 return nerr, nfail
123
130
124 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
131 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
125 if fail_m:
132 if fail_m:
126 nerr = 0
133 nerr = 0
127 nfail = int(fail_m.group(1))
134 nfail = int(fail_m.group(1))
128 return nerr, nfail
135 return nerr, nfail
129
136
130 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
137 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
131 re.MULTILINE)
138 re.MULTILINE)
132 if both_m:
139 if both_m:
133 nerr = int(both_m.group(1))
140 nerr = int(both_m.group(1))
134 nfail = int(both_m.group(2))
141 nfail = int(both_m.group(2))
135 return nerr, nfail
142 return nerr, nfail
136
143
137 # If the input didn't match any of these forms, assume no error/failures
144 # If the input didn't match any of these forms, assume no error/failures
138 return 0, 0
145 return 0, 0
139
146
140
147
141 # So nose doesn't think this is a test
148 # So nose doesn't think this is a test
142 parse_test_output.__test__ = False
149 parse_test_output.__test__ = False
143
150
144
151
152 def cmd2argv(cmd):
153 r"""Take the path of a command and return a list (argv-style).
154
155 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
156 .com or .bat, and ['python', cmd] otherwise.
157
158 This is mostly a Windows utility, to deal with the fact that the scripts in
159 Windows get wrapped in .exe entry points, so we have to call them
160 differently.
161
162 Parameters
163 ----------
164 cmd : string
165 The path of the command.
166
167 Returns
168 -------
169 argv-style list.
170
171 Examples
172 --------
173 In [2]: cmd2argv('/usr/bin/ipython')
174 Out[2]: ['python', '/usr/bin/ipython']
175
176 In [3]: cmd2argv(r'C:\Python26\Scripts\ipython.exe')
177 Out[3]: ['C:\\Python26\\Scripts\\ipython.exe']
178 """
179 ext = os.path.splitext(cmd)[1]
180 if ext in ['.exe', '.com', '.bat']:
181 return [cmd]
182 else:
183 return ['python', cmd]
184
185
145 def temp_pyfile(src, ext='.py'):
186 def temp_pyfile(src, ext='.py'):
146 """Make a temporary python file, return filename and filehandle.
187 """Make a temporary python file, return filename and filehandle.
147
188
148 Parameters
189 Parameters
149 ----------
190 ----------
150 src : string or list of strings (no need for ending newlines if list)
191 src : string or list of strings (no need for ending newlines if list)
151 Source code to be written to the file.
192 Source code to be written to the file.
152
193
153 ext : optional, string
194 ext : optional, string
154 Extension for the generated file.
195 Extension for the generated file.
155
196
156 Returns
197 Returns
157 -------
198 -------
158 (filename, open filehandle)
199 (filename, open filehandle)
159 It is the caller's responsibility to close the open file and unlink it.
200 It is the caller's responsibility to close the open file and unlink it.
160 """
201 """
161 fname = tempfile.mkstemp(ext)[1]
202 fname = tempfile.mkstemp(ext)[1]
162 f = open(fname,'w')
203 f = open(fname,'w')
163 f.write(src)
204 f.write(src)
164 f.flush()
205 f.flush()
165 return fname, f
206 return fname, f
166
207
167
208
168 def default_argv():
209 def default_argv():
169 """Return a valid default argv for creating testing instances of ipython"""
210 """Return a valid default argv for creating testing instances of ipython"""
170
211
171 # Get the install directory for the user configuration and tell ipython to
212 # Get the install directory for the user configuration and tell ipython to
172 # use the default profile from there.
213 # use the default profile from there.
173 from IPython.config import default
214 from IPython.config import default
174 ipcdir = os.path.dirname(default.__file__)
215 ipcdir = os.path.dirname(default.__file__)
175 ipconf = os.path.join(ipcdir,'ipython_config.py')
216 ipconf = os.path.join(ipcdir,'ipython_config.py')
176 return ['--colors=NoColor', '--no-term-title','--no-banner',
217 return ['--colors=NoColor', '--no-term-title','--no-banner',
177 '--config-file="%s"' % ipconf, '--autocall=0',
218 '--config-file="%s"' % ipconf, '--autocall=0',
178 '--prompt-out=""']
219 '--prompt-out=""']
179
220
180
221
181 def ipexec(fname, options=None):
222 def ipexec(fname, options=None):
182 """Utility to call 'ipython filename'.
223 """Utility to call 'ipython filename'.
183
224
184 Starts IPython witha minimal and safe configuration to make startup as fast
225 Starts IPython witha minimal and safe configuration to make startup as fast
185 as possible.
226 as possible.
186
227
187 Note that this starts IPython in a subprocess!
228 Note that this starts IPython in a subprocess!
188
229
189 Parameters
230 Parameters
190 ----------
231 ----------
191 fname : str
232 fname : str
192 Name of file to be executed (should have .py or .ipy extension).
233 Name of file to be executed (should have .py or .ipy extension).
193
234
194 options : optional, list
235 options : optional, list
195 Extra command-line flags to be passed to IPython.
236 Extra command-line flags to be passed to IPython.
196
237
197 Returns
238 Returns
198 -------
239 -------
199 (stdout, stderr) of ipython subprocess.
240 (stdout, stderr) of ipython subprocess.
200 """
241 """
201 if options is None: options = []
242 if options is None: options = []
202 cmdargs = ' '.join(default_argv() + options)
243 cmdargs = ' '.join(default_argv() + options)
203
244
204 _ip = get_ipython()
245 _ip = get_ipython()
205 test_dir = os.path.dirname(__file__)
246 test_dir = os.path.dirname(__file__)
206 # Find the ipython script from the package we're using, so that the test
247 # Find the ipython script from the package we're using, so that the test
207 # suite can be run from the source tree without an installed IPython
248 # suite can be run from the source tree without an installed IPython
208 ipython_package_dir = genutils.get_ipython_package_dir()
249 ipython_package_dir = genutils.get_ipython_package_dir()
209 ipython_script = os.path.join(ipython_package_dir,'scripts','ipython')
250 ipython_script = os.path.join(ipython_package_dir,'scripts','ipython')
210 ipython_cmd = 'python "%s"' % ipython_script
251 ipython_cmd = 'python "%s"' % ipython_script
211 # Absolute path for filename
252 # Absolute path for filename
212 full_fname = os.path.join(test_dir, fname)
253 full_fname = os.path.join(test_dir, fname)
213 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
254 full_cmd = '%s %s "%s"' % (ipython_cmd, cmdargs, full_fname)
214 return genutils.getoutputerror(full_cmd)
255 return genutils.getoutputerror(full_cmd)
215
256
216
257
217 def ipexec_validate(fname, expected_out, expected_err=None,
258 def ipexec_validate(fname, expected_out, expected_err=None,
218 options=None):
259 options=None):
219 """Utility to call 'ipython filename' and validate output/error.
260 """Utility to call 'ipython filename' and validate output/error.
220
261
221 This function raises an AssertionError if the validation fails.
262 This function raises an AssertionError if the validation fails.
222
263
223 Note that this starts IPython in a subprocess!
264 Note that this starts IPython in a subprocess!
224
265
225 Parameters
266 Parameters
226 ----------
267 ----------
227 fname : str
268 fname : str
228 Name of the file to be executed (should have .py or .ipy extension).
269 Name of the file to be executed (should have .py or .ipy extension).
229
270
230 expected_out : str
271 expected_out : str
231 Expected stdout of the process.
272 Expected stdout of the process.
232
273
233 expected_err : optional, str
274 expected_err : optional, str
234 Expected stderr of the process.
275 Expected stderr of the process.
235
276
236 options : optional, list
277 options : optional, list
237 Extra command-line flags to be passed to IPython.
278 Extra command-line flags to be passed to IPython.
238
279
239 Returns
280 Returns
240 -------
281 -------
241 None
282 None
242 """
283 """
243
284
244 import nose.tools as nt
285 import nose.tools as nt
245
286
246 out, err = ipexec(fname)
287 out, err = ipexec(fname)
247 nt.assert_equals(out.strip(), expected_out.strip())
288 nt.assert_equals(out.strip(), expected_out.strip())
248 if expected_err:
289 if expected_err:
249 nt.assert_equals(err.strip(), expected_err.strip())
290 nt.assert_equals(err.strip(), expected_err.strip())
250
291
251
292
252 class TempFileMixin(object):
293 class TempFileMixin(object):
253 """Utility class to create temporary Python/IPython files.
294 """Utility class to create temporary Python/IPython files.
254
295
255 Meant as a mixin class for test cases."""
296 Meant as a mixin class for test cases."""
256
297
257 def mktmp(self, src, ext='.py'):
298 def mktmp(self, src, ext='.py'):
258 """Make a valid python temp file."""
299 """Make a valid python temp file."""
259 fname, f = temp_pyfile(src, ext)
300 fname, f = temp_pyfile(src, ext)
260 self.tmpfile = f
301 self.tmpfile = f
261 self.fname = fname
302 self.fname = fname
262
303
263 def teardown(self):
304 def teardown(self):
264 if hasattr(self, 'tmpfile'):
305 if hasattr(self, 'tmpfile'):
265 # If the tmpfile wasn't made because of skipped tests, like in
306 # If the tmpfile wasn't made because of skipped tests, like in
266 # win32, there's nothing to cleanup.
307 # win32, there's nothing to cleanup.
267 self.tmpfile.close()
308 self.tmpfile.close()
268 try:
309 try:
269 os.unlink(self.fname)
310 os.unlink(self.fname)
270 except:
311 except:
271 # On Windows, even though we close the file, we still can't
312 # On Windows, even though we close the file, we still can't
272 # delete it. I have no clue why
313 # delete it. I have no clue why
273 pass
314 pass
274
315
General Comments 0
You need to be logged in to leave comments. Login now