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