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