##// END OF EJS Templates
Remove parametric testing machinery
Thomas Kluyver -
Show More
@@ -1,391 +1,381 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Decorators for labeling test objects.
3 3
4 4 Decorators that merely return a modified version of the original function
5 5 object are straightforward. Decorators that return a new function object need
6 6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 7 decorator, in order to preserve metadata such as function name, setup and
8 8 teardown functions and so on - see nose.tools for more information.
9 9
10 10 This module provides a set of useful decorators meant to be ready to use in
11 11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 12 find yourself writing a new one that may be of generic use, add it here.
13 13
14 14 Included decorators:
15 15
16 16
17 17 Lightweight testing that remains unittest-compatible.
18 18
19 - @parametric, for parametric test support that is vastly easier to use than
20 nose's for debugging. With ours, if a test fails, the stack under inspection
21 is that of the test and not that of the test framework.
22
23 19 - An @as_unittest decorator can be used to tag any normal parameter-less
24 20 function as a unittest TestCase. Then, both nose and normal unittest will
25 21 recognize it as such. This will make it easier to migrate away from Nose if
26 22 we ever need/want to while maintaining very lightweight tests.
27 23
28 24 NOTE: This file contains IPython-specific decorators. Using the machinery in
29 25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
30 26 available, OR use equivalent code in IPython.external._decorators, which
31 27 we've copied verbatim from numpy.
32 28
33 29 Authors
34 30 -------
35 31
36 32 - Fernando Perez <Fernando.Perez@berkeley.edu>
37 33 """
38 34
39 35 #-----------------------------------------------------------------------------
40 36 # Copyright (C) 2009-2011 The IPython Development Team
41 37 #
42 38 # Distributed under the terms of the BSD License. The full license is in
43 39 # the file COPYING, distributed as part of this software.
44 40 #-----------------------------------------------------------------------------
45 41
46 42 #-----------------------------------------------------------------------------
47 43 # Imports
48 44 #-----------------------------------------------------------------------------
49 45
50 46 # Stdlib imports
51 47 import sys
52 48 import os
53 49 import tempfile
54 50 import unittest
55 51
56 52 # Third-party imports
57 53
58 54 # This is Michele Simionato's decorator module, kept verbatim.
59 55 from IPython.external.decorator import decorator
60 56
61 # We already have python3-compliant code for parametric tests
62 if sys.version[0]=='2':
63 from _paramtestpy2 import parametric
64 else:
65 from _paramtestpy3 import parametric
66
67 57 # Expose the unittest-driven decorators
68 58 from ipunittest import ipdoctest, ipdocstring
69 59
70 60 # Grab the numpy-specific decorators which we keep in a file that we
71 61 # occasionally update from upstream: decorators.py is a copy of
72 62 # numpy.testing.decorators, we expose all of it here.
73 63 from IPython.external.decorators import *
74 64
75 65 # For onlyif_cmd_exists decorator
76 66 from IPython.utils.process import is_cmd_found
77 67
78 68 #-----------------------------------------------------------------------------
79 69 # Classes and functions
80 70 #-----------------------------------------------------------------------------
81 71
82 72 # Simple example of the basic idea
83 73 def as_unittest(func):
84 74 """Decorator to make a simple function into a normal test via unittest."""
85 75 class Tester(unittest.TestCase):
86 76 def test(self):
87 77 func()
88 78
89 79 Tester.__name__ = func.__name__
90 80
91 81 return Tester
92 82
93 83 # Utility functions
94 84
95 85 def apply_wrapper(wrapper,func):
96 86 """Apply a wrapper to a function for decoration.
97 87
98 88 This mixes Michele Simionato's decorator tool with nose's make_decorator,
99 89 to apply a wrapper in a decorator so that all nose attributes, as well as
100 90 function signature and other properties, survive the decoration cleanly.
101 91 This will ensure that wrapped functions can still be well introspected via
102 92 IPython, for example.
103 93 """
104 94 import nose.tools
105 95
106 96 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
107 97
108 98
109 99 def make_label_dec(label,ds=None):
110 100 """Factory function to create a decorator that applies one or more labels.
111 101
112 102 Parameters
113 103 ----------
114 104 label : string or sequence
115 105 One or more labels that will be applied by the decorator to the functions
116 106 it decorates. Labels are attributes of the decorated function with their
117 107 value set to True.
118 108
119 109 ds : string
120 110 An optional docstring for the resulting decorator. If not given, a
121 111 default docstring is auto-generated.
122 112
123 113 Returns
124 114 -------
125 115 A decorator.
126 116
127 117 Examples
128 118 --------
129 119
130 120 A simple labeling decorator:
131 121
132 122 >>> slow = make_label_dec('slow')
133 123 >>> slow.__doc__
134 124 "Labels a test as 'slow'."
135 125
136 126 And one that uses multiple labels and a custom docstring:
137 127
138 128 >>> rare = make_label_dec(['slow','hard'],
139 129 ... "Mix labels 'slow' and 'hard' for rare tests.")
140 130 >>> rare.__doc__
141 131 "Mix labels 'slow' and 'hard' for rare tests."
142 132
143 133 Now, let's test using this one:
144 134 >>> @rare
145 135 ... def f(): pass
146 136 ...
147 137 >>>
148 138 >>> f.slow
149 139 True
150 140 >>> f.hard
151 141 True
152 142 """
153 143
154 144 if isinstance(label,basestring):
155 145 labels = [label]
156 146 else:
157 147 labels = label
158 148
159 149 # Validate that the given label(s) are OK for use in setattr() by doing a
160 150 # dry run on a dummy function.
161 151 tmp = lambda : None
162 152 for label in labels:
163 153 setattr(tmp,label,True)
164 154
165 155 # This is the actual decorator we'll return
166 156 def decor(f):
167 157 for label in labels:
168 158 setattr(f,label,True)
169 159 return f
170 160
171 161 # Apply the user's docstring, or autogenerate a basic one
172 162 if ds is None:
173 163 ds = "Labels a test as %r." % label
174 164 decor.__doc__ = ds
175 165
176 166 return decor
177 167
178 168
179 169 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
180 170 # preserve function metadata better and allows the skip condition to be a
181 171 # callable.
182 172 def skipif(skip_condition, msg=None):
183 173 ''' Make function raise SkipTest exception if skip_condition is true
184 174
185 175 Parameters
186 176 ----------
187 177 skip_condition : bool or callable.
188 178 Flag to determine whether to skip test. If the condition is a
189 179 callable, it is used at runtime to dynamically make the decision. This
190 180 is useful for tests that may require costly imports, to delay the cost
191 181 until the test suite is actually executed.
192 182 msg : string
193 183 Message to give on raising a SkipTest exception
194 184
195 185 Returns
196 186 -------
197 187 decorator : function
198 188 Decorator, which, when applied to a function, causes SkipTest
199 189 to be raised when the skip_condition was True, and the function
200 190 to be called normally otherwise.
201 191
202 192 Notes
203 193 -----
204 194 You will see from the code that we had to further decorate the
205 195 decorator with the nose.tools.make_decorator function in order to
206 196 transmit function name, and various other metadata.
207 197 '''
208 198
209 199 def skip_decorator(f):
210 200 # Local import to avoid a hard nose dependency and only incur the
211 201 # import time overhead at actual test-time.
212 202 import nose
213 203
214 204 # Allow for both boolean or callable skip conditions.
215 205 if callable(skip_condition):
216 206 skip_val = skip_condition
217 207 else:
218 208 skip_val = lambda : skip_condition
219 209
220 210 def get_msg(func,msg=None):
221 211 """Skip message with information about function being skipped."""
222 212 if msg is None: out = 'Test skipped due to test condition.'
223 213 else: out = msg
224 214 return "Skipping test: %s. %s" % (func.__name__,out)
225 215
226 216 # We need to define *two* skippers because Python doesn't allow both
227 217 # return with value and yield inside the same function.
228 218 def skipper_func(*args, **kwargs):
229 219 """Skipper for normal test functions."""
230 220 if skip_val():
231 221 raise nose.SkipTest(get_msg(f,msg))
232 222 else:
233 223 return f(*args, **kwargs)
234 224
235 225 def skipper_gen(*args, **kwargs):
236 226 """Skipper for test generators."""
237 227 if skip_val():
238 228 raise nose.SkipTest(get_msg(f,msg))
239 229 else:
240 230 for x in f(*args, **kwargs):
241 231 yield x
242 232
243 233 # Choose the right skipper to use when building the actual generator.
244 234 if nose.util.isgenerator(f):
245 235 skipper = skipper_gen
246 236 else:
247 237 skipper = skipper_func
248 238
249 239 return nose.tools.make_decorator(f)(skipper)
250 240
251 241 return skip_decorator
252 242
253 243 # A version with the condition set to true, common case just to attach a message
254 244 # to a skip decorator
255 245 def skip(msg=None):
256 246 """Decorator factory - mark a test function for skipping from test suite.
257 247
258 248 Parameters
259 249 ----------
260 250 msg : string
261 251 Optional message to be added.
262 252
263 253 Returns
264 254 -------
265 255 decorator : function
266 256 Decorator, which, when applied to a function, causes SkipTest
267 257 to be raised, with the optional message added.
268 258 """
269 259
270 260 return skipif(True,msg)
271 261
272 262
273 263 def onlyif(condition, msg):
274 264 """The reverse from skipif, see skipif for details."""
275 265
276 266 if callable(condition):
277 267 skip_condition = lambda : not condition()
278 268 else:
279 269 skip_condition = lambda : not condition
280 270
281 271 return skipif(skip_condition, msg)
282 272
283 273 #-----------------------------------------------------------------------------
284 274 # Utility functions for decorators
285 275 def module_not_available(module):
286 276 """Can module be imported? Returns true if module does NOT import.
287 277
288 278 This is used to make a decorator to skip tests that require module to be
289 279 available, but delay the 'import numpy' to test execution time.
290 280 """
291 281 try:
292 282 mod = __import__(module)
293 283 mod_not_avail = False
294 284 except ImportError:
295 285 mod_not_avail = True
296 286
297 287 return mod_not_avail
298 288
299 289
300 290 def decorated_dummy(dec, name):
301 291 """Return a dummy function decorated with dec, with the given name.
302 292
303 293 Examples
304 294 --------
305 295 import IPython.testing.decorators as dec
306 296 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
307 297 """
308 298 dummy = lambda: None
309 299 dummy.__name__ = name
310 300 return dec(dummy)
311 301
312 302 #-----------------------------------------------------------------------------
313 303 # Decorators for public use
314 304
315 305 # Decorators to skip certain tests on specific platforms.
316 306 skip_win32 = skipif(sys.platform == 'win32',
317 307 "This test does not run under Windows")
318 308 skip_linux = skipif(sys.platform.startswith('linux'),
319 309 "This test does not run under Linux")
320 310 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
321 311
322 312
323 313 # Decorators to skip tests if not on specific platforms.
324 314 skip_if_not_win32 = skipif(sys.platform != 'win32',
325 315 "This test only runs under Windows")
326 316 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
327 317 "This test only runs under Linux")
328 318 skip_if_not_osx = skipif(sys.platform != 'darwin',
329 319 "This test only runs under OSX")
330 320
331 321
332 322 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
333 323 os.environ.get('DISPLAY', '') == '')
334 324 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
335 325
336 326 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
337 327
338 328 # not a decorator itself, returns a dummy function to be used as setup
339 329 def skip_file_no_x11(name):
340 330 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
341 331
342 332 # Other skip decorators
343 333
344 334 # generic skip without module
345 335 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
346 336
347 337 skipif_not_numpy = skip_without('numpy')
348 338
349 339 skipif_not_matplotlib = skip_without('matplotlib')
350 340
351 341 skipif_not_sympy = skip_without('sympy')
352 342
353 343 skip_known_failure = knownfailureif(True,'This test is known to fail')
354 344
355 345 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
356 346 'This test is known to fail on Python 3.')
357 347
358 348 # A null 'decorator', useful to make more readable code that needs to pick
359 349 # between different decorators based on OS or other conditions
360 350 null_deco = lambda f: f
361 351
362 352 # Some tests only run where we can use unicode paths. Note that we can't just
363 353 # check os.path.supports_unicode_filenames, which is always False on Linux.
364 354 try:
365 355 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
366 356 except UnicodeEncodeError:
367 357 unicode_paths = False
368 358 else:
369 359 unicode_paths = True
370 360 f.close()
371 361
372 362 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
373 363 "where we can use unicode in filenames."))
374 364
375 365
376 366 def onlyif_cmds_exist(*commands):
377 367 """
378 368 Decorator to skip test when at least one of `commands` is not found.
379 369 """
380 370 for cmd in commands:
381 371 try:
382 372 if not is_cmd_found(cmd):
383 373 return skip("This test runs only if command '{0}' "
384 374 "is installed".format(cmd))
385 375 except ImportError as e:
386 376 # is_cmd_found uses pywin32 on windows, which might not be available
387 377 if sys.platform == 'win32' and 'pywin32' in e.message:
388 378 return skip("This test runs only if pywin32 and command '{0}' "
389 379 "is installed".format(cmd))
390 380 raise e
391 381 return null_deco
@@ -1,184 +1,178 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-2011 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 # Imports
36 36 #-----------------------------------------------------------------------------
37 37
38 38 # Stdlib
39 39 import re
40 40 import sys
41 41 import unittest
42 42 from doctest import DocTestFinder, DocTestRunner, TestResults
43 43
44 # We already have python3-compliant code for parametric tests
45 if sys.version[0]=='2':
46 from ._paramtestpy2 import ParametricTestCase
47 else:
48 from ._paramtestpy3 import ParametricTestCase
49
50 44 #-----------------------------------------------------------------------------
51 45 # Classes and functions
52 46 #-----------------------------------------------------------------------------
53 47
54 48 def count_failures(runner):
55 49 """Count number of failures in a doctest runner.
56 50
57 51 Code modeled after the summarize() method in doctest.
58 52 """
59 53 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
60 54
61 55
62 56 class IPython2PythonConverter(object):
63 57 """Convert IPython 'syntax' to valid Python.
64 58
65 59 Eventually this code may grow to be the full IPython syntax conversion
66 60 implementation, but for now it only does prompt convertion."""
67 61
68 62 def __init__(self):
69 63 self.rps1 = re.compile(r'In\ \[\d+\]: ')
70 64 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
71 65 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
72 66 self.pyps1 = '>>> '
73 67 self.pyps2 = '... '
74 68 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
75 69 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
76 70
77 71 def __call__(self, ds):
78 72 """Convert IPython prompts to python ones in a string."""
79 73 from . import globalipapp
80 74
81 75 pyps1 = '>>> '
82 76 pyps2 = '... '
83 77 pyout = ''
84 78
85 79 dnew = ds
86 80 dnew = self.rps1.sub(pyps1, dnew)
87 81 dnew = self.rps2.sub(pyps2, dnew)
88 82 dnew = self.rout.sub(pyout, dnew)
89 83 ip = globalipapp.get_ipython()
90 84
91 85 # Convert input IPython source into valid Python.
92 86 out = []
93 87 newline = out.append
94 88 for line in dnew.splitlines():
95 89
96 90 mps1 = self.rpyps1.match(line)
97 91 if mps1 is not None:
98 92 prompt, text = mps1.groups()
99 93 newline(prompt+ip.prefilter(text, False))
100 94 continue
101 95
102 96 mps2 = self.rpyps2.match(line)
103 97 if mps2 is not None:
104 98 prompt, text = mps2.groups()
105 99 newline(prompt+ip.prefilter(text, True))
106 100 continue
107 101
108 102 newline(line)
109 103 newline('') # ensure a closing newline, needed by doctest
110 104 #print "PYSRC:", '\n'.join(out) # dbg
111 105 return '\n'.join(out)
112 106
113 107 #return dnew
114 108
115 109
116 110 class Doc2UnitTester(object):
117 111 """Class whose instances act as a decorator for docstring testing.
118 112
119 113 In practice we're only likely to need one instance ever, made below (though
120 114 no attempt is made at turning it into a singleton, there is no need for
121 115 that).
122 116 """
123 117 def __init__(self, verbose=False):
124 118 """New decorator.
125 119
126 120 Parameters
127 121 ----------
128 122
129 123 verbose : boolean, optional (False)
130 124 Passed to the doctest finder and runner to control verbosity.
131 125 """
132 126 self.verbose = verbose
133 127 # We can reuse the same finder for all instances
134 128 self.finder = DocTestFinder(verbose=verbose, recurse=False)
135 129
136 130 def __call__(self, func):
137 131 """Use as a decorator: doctest a function's docstring as a unittest.
138 132
139 133 This version runs normal doctests, but the idea is to make it later run
140 134 ipython syntax instead."""
141 135
142 136 # Capture the enclosing instance with a different name, so the new
143 137 # class below can see it without confusion regarding its own 'self'
144 138 # that will point to the test instance at runtime
145 139 d2u = self
146 140
147 141 # Rewrite the function's docstring to have python syntax
148 142 if func.__doc__ is not None:
149 143 func.__doc__ = ip2py(func.__doc__)
150 144
151 145 # Now, create a tester object that is a real unittest instance, so
152 146 # normal unittest machinery (or Nose, or Trial) can find it.
153 147 class Tester(unittest.TestCase):
154 148 def test(self):
155 149 # Make a new runner per function to be tested
156 150 runner = DocTestRunner(verbose=d2u.verbose)
157 151 map(runner.run, d2u.finder.find(func, func.__name__))
158 152 failed = count_failures(runner)
159 153 if failed:
160 154 # Since we only looked at a single function's docstring,
161 155 # failed should contain at most one item. More than that
162 156 # is a case we can't handle and should error out on
163 157 if len(failed) > 1:
164 158 err = "Invalid number of test results:" % failed
165 159 raise ValueError(err)
166 160 # Report a normal failure.
167 161 self.fail('failed doctests: %s' % str(failed[0]))
168 162
169 163 # Rename it so test reports have the original signature.
170 164 Tester.__name__ = func.__name__
171 165 return Tester
172 166
173 167
174 168 def ipdocstring(func):
175 169 """Change the function docstring via ip2py.
176 170 """
177 171 if func.__doc__ is not None:
178 172 func.__doc__ = ip2py(func.__doc__)
179 173 return func
180 174
181 175
182 176 # Make an instance of the classes for public use
183 177 ipdoctest = Doc2UnitTester()
184 178 ip2py = IPython2PythonConverter()
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now