##// END OF EJS Templates
Add new testing support machinery with better parametric tests....
Fernando Perez -
Show More
@@ -0,0 +1,156 b''
1 """Experimental code for cleaner support of IPython syntax with unittest.
2
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
4 tests with IPython special syntax, and this has proved to be extremely slow.
5 This module provides decorators to try a different approach, stemming from a
6 conversation Brian and I (FP) had about this problem Sept/09.
7
8 The goal is to be able to easily write simple functions that can be seen by
9 unittest as tests, and ultimately for these to support doctests with full
10 IPython syntax. Nose already offers this based on naming conventions and our
11 hackish plugins, but we are seeking to move away from nose dependencies if
12 possible.
13
14 This module follows a different approach, based on decorators.
15
16 - A decorator called @ipdoctest can mark any function as having a docstring
17 that should be viewed as a doctest, but after syntax conversion.
18
19 Authors
20 -------
21
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 """
24
25 #-----------------------------------------------------------------------------
26 # Copyright (C) 2009 The IPython Development Team
27 #
28 # Distributed under the terms of the BSD License. The full license is in
29 # the file COPYING, distributed as part of this software.
30 #-----------------------------------------------------------------------------
31
32
33 #-----------------------------------------------------------------------------
34 # Imports
35 #-----------------------------------------------------------------------------
36
37 # Stdlib
38 import re
39 import sys
40 import unittest
41 from doctest import DocTestFinder, DocTestRunner, TestResults
42
43 # Our own
44 import nosepatch
45
46 # We already have python3-compliant code for parametric tests
47 if sys.version[0]=='2':
48 from _paramtestpy2 import ParametricTestCase
49 else:
50 from _paramtestpy3 import ParametricTestCase
51
52 #-----------------------------------------------------------------------------
53 # Classes and functions
54 #-----------------------------------------------------------------------------
55
56 def count_failures(runner):
57 """Count number of failures in a doctest runner.
58
59 Code modeled after the summarize() method in doctest.
60 """
61 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
62
63
64 class IPython2PythonConverter(object):
65 """Convert IPython 'syntax' to valid Python.
66
67 Eventually this code may grow to be the full IPython syntax conversion
68 implementation, but for now it only does prompt convertion."""
69
70 def __init__(self):
71 self.ps1 = re.compile(r'In\ \[\d+\]: ')
72 self.ps2 = re.compile(r'\ \ \ \.\.\.+: ')
73 self.out = re.compile(r'Out\[\d+\]: \s*?\n?')
74
75 def __call__(self, ds):
76 """Convert IPython prompts to python ones in a string."""
77 pyps1 = '>>> '
78 pyps2 = '... '
79 pyout = ''
80
81 dnew = ds
82 dnew = self.ps1.sub(pyps1, dnew)
83 dnew = self.ps2.sub(pyps2, dnew)
84 dnew = self.out.sub(pyout, dnew)
85 return dnew
86
87
88 class Doc2UnitTester(object):
89 """Class whose instances act as a decorator for docstring testing.
90
91 In practice we're only likely to need one instance ever, made below (though
92 no attempt is made at turning it into a singleton, there is no need for
93 that).
94 """
95 def __init__(self, verbose=False):
96 """New decorator.
97
98 Parameters
99 ----------
100
101 verbose : boolean, optional (False)
102 Passed to the doctest finder and runner to control verbosity.
103 """
104 self.verbose = verbose
105 # We can reuse the same finder for all instances
106 self.finder = DocTestFinder(verbose=verbose, recurse=False)
107
108 def __call__(self, func):
109 """Use as a decorator: doctest a function's docstring as a unittest.
110
111 This version runs normal doctests, but the idea is to make it later run
112 ipython syntax instead."""
113
114 # Capture the enclosing instance with a different name, so the new
115 # class below can see it without confusion regarding its own 'self'
116 # that will point to the test instance at runtime
117 d2u = self
118
119 # Rewrite the function's docstring to have python syntax
120 if func.__doc__ is not None:
121 func.__doc__ = ip2py(func.__doc__)
122
123 # Now, create a tester object that is a real unittest instance, so
124 # normal unittest machinery (or Nose, or Trial) can find it.
125 class Tester(unittest.TestCase):
126 def test(self):
127 # Make a new runner per function to be tested
128 runner = DocTestRunner(verbose=d2u.verbose)
129 map(runner.run, d2u.finder.find(func, func.__name__))
130 failed = count_failures(runner)
131 if failed:
132 # Since we only looked at a single function's docstring,
133 # failed should contain at most one item. More than that
134 # is a case we can't handle and should error out on
135 if len(failed) > 1:
136 err = "Invalid number of test results:" % failed
137 raise ValueError(err)
138 # Report a normal failure.
139 self.fail('failed doctests: %s' % str(failed[0]))
140
141 # Rename it so test reports have the original signature.
142 Tester.__name__ = func.__name__
143 return Tester
144
145
146 def ipdocstring(func):
147 """Change the function docstring via ip2py.
148 """
149 if func.__doc__ is not None:
150 func.__doc__ = ip2py(func.__doc__)
151 return func
152
153
154 # Make an instance of the classes for public use
155 ipdoctest = Doc2UnitTester()
156 ip2py = IPython2PythonConverter()
@@ -0,0 +1,122 b''
1 """Tests for IPyhton's test support utilities.
2
3 These are decorators that allow standalone functions and docstrings to be seen
4 as tests by unittest, replicating some of nose's functionality. Additionally,
5 IPython-syntax docstrings can be auto-converted to '>>>' so that ipython
6 sessions can be copy-pasted as tests.
7
8 This file can be run as a script, and it will call unittest.main(). We must
9 check that it works with unittest as well as with nose...
10
11
12 Notes:
13
14 - Using nosetests --with-doctest --doctest-tests testfile.py
15 will find docstrings as tests wherever they are, even in methods. But
16 if we use ipython syntax in the docstrings, they must be decorated with
17 @ipdocstring. This is OK for test-only code, but not for user-facing
18 docstrings where we want to keep the ipython syntax.
19
20 - Using nosetests --with-doctest file.py
21 also finds doctests if the file name doesn't have 'test' in it, because it is
22 treated like a normal module. But if nose treats the file like a test file,
23 then for normal classes to be doctested the extra --doctest-tests is
24 necessary.
25
26 - running this script with python (it has a __main__ section at the end) misses
27 one docstring test, the one embedded in the Foo object method. Since our
28 approach relies on using decorators that create standalone TestCase
29 instances, it can only be used for functions, not for methods of objects.
30 Authors
31 -------
32
33 - Fernando Perez <Fernando.Perez@berkeley.edu>
34 """
35
36 #-----------------------------------------------------------------------------
37 # Copyright (C) 2009 The IPython Development Team
38 #
39 # Distributed under the terms of the BSD License. The full license is in
40 # the file COPYING, distributed as part of this software.
41 #-----------------------------------------------------------------------------
42
43
44 #-----------------------------------------------------------------------------
45 # Imports
46 #-----------------------------------------------------------------------------
47
48 from IPython.testing.ipunittest import ipdoctest, ipdocstring
49
50 #-----------------------------------------------------------------------------
51 # Test classes and functions
52 #-----------------------------------------------------------------------------
53 @ipdoctest
54 def simple_dt():
55 """
56 >>> print 1+1
57 2
58 """
59
60
61 @ipdoctest
62 def ipdt_flush():
63 """
64 In [20]: print 1
65 1
66
67 In [26]: for i in range(10):
68 ....: print i,
69 ....:
70 ....:
71 0 1 2 3 4 5 6 7 8 9
72
73 In [27]: 3+4
74 Out[27]: 7
75 """
76
77
78 @ipdoctest
79 def ipdt_indented_test():
80 """
81 In [20]: print 1
82 1
83
84 In [26]: for i in range(10):
85 ....: print i,
86 ....:
87 ....:
88 0 1 2 3 4 5 6 7 8 9
89
90 In [27]: 3+4
91 Out[27]: 7
92 """
93
94
95 class Foo(object):
96 """For methods, the normal decorator doesn't work.
97
98 But rewriting the docstring with ip2py does, *but only if using nose
99 --with-doctest*. Do we want to have that as a dependency?
100 """
101
102 @ipdocstring
103 def ipdt_method(self):
104 """
105 In [20]: print 1
106 2
107
108 In [26]: for i in range(10):
109 ....: print i,
110 ....:
111 ....:
112 0 1 2 3 4 5 6 7 8 9
113
114 In [27]: 3+4
115 Out[27]: 7
116 """
117
118 def normaldt_method(self):
119 """
120 >>> print 1+1
121 2
122 """
1 NO CONTENT: file renamed from IPython/testing/decorators_numpy.py to IPython/external/decorators.py
@@ -1,78 +1,77 b''
1 1 # encoding: utf-8
2 2 """
3 3 Test the output capture at the OS level, using file descriptors.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2008-2009 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is
9 9 # in the file COPYING, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 # Tell nose to skip this module
17 17 __test__ = {}
18 18
19 19 from cStringIO import StringIO
20 20 import os
21 21
22 22 from twisted.trial import unittest
23 23
24 from IPython.testing import decorators_trial as dec
24 from IPython.testing import decorators as dec
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Tests
28 28 #-----------------------------------------------------------------------------
29 29
30
31 30 class TestRedirector(unittest.TestCase):
32 31
33 32 @dec.skip_win32
34 33 def test_redirector(self):
35 34 """Checks that the redirector can be used to do synchronous capture.
36 35 """
37 36 from IPython.kernel.core.fd_redirector import FDRedirector
38 37 r = FDRedirector()
39 38 out = StringIO()
40 39 try:
41 40 r.start()
42 41 for i in range(10):
43 42 os.system('echo %ic' % i)
44 43 print >>out, r.getvalue(),
45 44 print >>out, i
46 45 except:
47 46 r.stop()
48 47 raise
49 48 r.stop()
50 49 result1 = out.getvalue()
51 50 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
52 51 self.assertEquals(result1, result2)
53 52
54 53 @dec.skip_win32
55 54 def test_redirector_output_trap(self):
56 55 """Check the greedy trapping behavior of the traps.
57 56
58 57 This test check not only that the redirector_output_trap does
59 58 trap the output, but also that it does it in a gready way, that
60 59 is by calling the callback ASAP.
61 60 """
62 61 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
63 62 out = StringIO()
64 63 trap = RedirectorOutputTrap(out.write, out.write)
65 64 try:
66 65 trap.set()
67 66 for i in range(10):
68 67 os.system('echo %ic' % i)
69 68 print "%ip" % i
70 69 print >>out, i
71 70 except:
72 71 trap.unset()
73 72 raise
74 73 trap.unset()
75 74 result1 = out.getvalue()
76 75 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
77 76 self.assertEquals(result1, result2)
78 77
@@ -1,254 +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 Included decorators:
14
15
16 Lightweight testing that remains unittest-compatible.
17
18 - @parametric, for parametric test support that is vastly easier to use than
19 nose's for debugging. With ours, if a test fails, the stack under inspection
20 is that of the test and not that of the test framework.
21
22 - An @as_unittest decorator can be used to tag any normal parameter-less
23 function as a unittest TestCase. Then, both nose and normal unittest will
24 recognize it as such. This will make it easier to migrate away from Nose if
25 we ever need/want to while maintaining very lightweight tests.
26
13 27 NOTE: This file contains IPython-specific decorators and imports the
14 28 numpy.testing.decorators file, which we've copied verbatim. Any of our own
15 29 code will be added at the bottom if we end up extending this.
30
31 Authors
32 -------
33
34 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 35 """
17 36
37 #-----------------------------------------------------------------------------
38 # Copyright (C) 2009-2010 The IPython Development Team
39 #
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
42 #-----------------------------------------------------------------------------
43
44 #-----------------------------------------------------------------------------
45 # Imports
46 #-----------------------------------------------------------------------------
47
18 48 # Stdlib imports
19 49 import inspect
20 50 import sys
51 import unittest
21 52
22 53 # Third-party imports
23 54
24 # This is Michele Simionato's decorator module, also kept verbatim.
55 # This is Michele Simionato's decorator module, kept verbatim.
25 56 from IPython.external.decorator import decorator, update_wrapper
26 57
58 # Our own modules
59 import nosepatch # monkeypatch nose
60
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
27 67 # Grab the numpy-specific decorators which we keep in a file that we
28 # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy
29 # of numpy.testing.decorators.
30 from decorators_numpy import *
68 # occasionally update from upstream: decorators.py is a copy of
69 # numpy.testing.decorators, we expose all of it here.
70 from IPython.external.decorators import *
71
72 #-----------------------------------------------------------------------------
73 # Classes and functions
74 #-----------------------------------------------------------------------------
75
76 # Simple example of the basic idea
77 def as_unittest(func):
78 """Decorator to make a simple function into a normal test via unittest."""
79 class Tester(unittest.TestCase):
80 def test(self):
81 func()
31 82
32 ##############################################################################
33 # Local code begins
83 Tester.__name__ = func.__name__
84
85 return Tester
34 86
35 87 # Utility functions
36 88
37 89 def apply_wrapper(wrapper,func):
38 90 """Apply a wrapper to a function for decoration.
39 91
40 92 This mixes Michele Simionato's decorator tool with nose's make_decorator,
41 93 to apply a wrapper in a decorator so that all nose attributes, as well as
42 94 function signature and other properties, survive the decoration cleanly.
43 95 This will ensure that wrapped functions can still be well introspected via
44 96 IPython, for example.
45 97 """
46 98 import nose.tools
47 99
48 100 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
49 101
50 102
51 103 def make_label_dec(label,ds=None):
52 104 """Factory function to create a decorator that applies one or more labels.
53 105
54 :Parameters:
106 Parameters
107 ----------
55 108 label : string or sequence
56 109 One or more labels that will be applied by the decorator to the functions
57 110 it decorates. Labels are attributes of the decorated function with their
58 111 value set to True.
59 112
60 :Keywords:
61 113 ds : string
62 114 An optional docstring for the resulting decorator. If not given, a
63 115 default docstring is auto-generated.
64 116
65 :Returns:
117 Returns
118 -------
66 119 A decorator.
67 120
68 :Examples:
121 Examples
122 --------
69 123
70 124 A simple labeling decorator:
71 125 >>> slow = make_label_dec('slow')
72 126 >>> print slow.__doc__
73 127 Labels a test as 'slow'.
74 128
75 129 And one that uses multiple labels and a custom docstring:
76 130 >>> rare = make_label_dec(['slow','hard'],
77 131 ... "Mix labels 'slow' and 'hard' for rare tests.")
78 132 >>> print rare.__doc__
79 133 Mix labels 'slow' and 'hard' for rare tests.
80 134
81 135 Now, let's test using this one:
82 136 >>> @rare
83 137 ... def f(): pass
84 138 ...
85 139 >>>
86 140 >>> f.slow
87 141 True
88 142 >>> f.hard
89 143 True
90 144 """
91 145
92 146 if isinstance(label,basestring):
93 147 labels = [label]
94 148 else:
95 149 labels = label
96 150
97 151 # Validate that the given label(s) are OK for use in setattr() by doing a
98 152 # dry run on a dummy function.
99 153 tmp = lambda : None
100 154 for label in labels:
101 155 setattr(tmp,label,True)
102 156
103 157 # This is the actual decorator we'll return
104 158 def decor(f):
105 159 for label in labels:
106 160 setattr(f,label,True)
107 161 return f
108 162
109 163 # Apply the user's docstring, or autogenerate a basic one
110 164 if ds is None:
111 165 ds = "Labels a test as %r." % label
112 166 decor.__doc__ = ds
113 167
114 168 return decor
115 169
116 170
117 171 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
118 172 # preserve function metadata better and allows the skip condition to be a
119 173 # callable.
120 174 def skipif(skip_condition, msg=None):
121 175 ''' Make function raise SkipTest exception if skip_condition is true
122 176
123 177 Parameters
124 178 ----------
125 179 skip_condition : bool or callable.
126 180 Flag to determine whether to skip test. If the condition is a
127 181 callable, it is used at runtime to dynamically make the decision. This
128 182 is useful for tests that may require costly imports, to delay the cost
129 183 until the test suite is actually executed.
130 184 msg : string
131 185 Message to give on raising a SkipTest exception
132 186
133 187 Returns
134 188 -------
135 189 decorator : function
136 190 Decorator, which, when applied to a function, causes SkipTest
137 191 to be raised when the skip_condition was True, and the function
138 192 to be called normally otherwise.
139 193
140 194 Notes
141 195 -----
142 196 You will see from the code that we had to further decorate the
143 197 decorator with the nose.tools.make_decorator function in order to
144 198 transmit function name, and various other metadata.
145 199 '''
146 200
147 201 def skip_decorator(f):
148 202 # Local import to avoid a hard nose dependency and only incur the
149 203 # import time overhead at actual test-time.
150 204 import nose
151 205
152 206 # Allow for both boolean or callable skip conditions.
153 207 if callable(skip_condition):
154 208 skip_val = lambda : skip_condition()
155 209 else:
156 210 skip_val = lambda : skip_condition
157 211
158 212 def get_msg(func,msg=None):
159 213 """Skip message with information about function being skipped."""
160 214 if msg is None: out = 'Test skipped due to test condition.'
161 215 else: out = msg
162 216 return "Skipping test: %s. %s" % (func.__name__,out)
163 217
164 218 # We need to define *two* skippers because Python doesn't allow both
165 219 # return with value and yield inside the same function.
166 220 def skipper_func(*args, **kwargs):
167 221 """Skipper for normal test functions."""
168 222 if skip_val():
169 223 raise nose.SkipTest(get_msg(f,msg))
170 224 else:
171 225 return f(*args, **kwargs)
172 226
173 227 def skipper_gen(*args, **kwargs):
174 228 """Skipper for test generators."""
175 229 if skip_val():
176 230 raise nose.SkipTest(get_msg(f,msg))
177 231 else:
178 232 for x in f(*args, **kwargs):
179 233 yield x
180 234
181 235 # Choose the right skipper to use when building the actual generator.
182 236 if nose.util.isgenerator(f):
183 237 skipper = skipper_gen
184 238 else:
185 239 skipper = skipper_func
186 240
187 241 return nose.tools.make_decorator(f)(skipper)
188 242
189 243 return skip_decorator
190 244
191 245 # A version with the condition set to true, common case just to attacha message
192 246 # to a skip decorator
193 247 def skip(msg=None):
194 248 """Decorator factory - mark a test function for skipping from test suite.
195 249
196 :Parameters:
250 Parameters
251 ----------
197 252 msg : string
198 253 Optional message to be added.
199 254
200 :Returns:
255 Returns
256 -------
201 257 decorator : function
202 258 Decorator, which, when applied to a function, causes SkipTest
203 259 to be raised, with the optional message added.
204 260 """
205 261
206 262 return skipif(True,msg)
207 263
208 264
209 265 #-----------------------------------------------------------------------------
210 266 # Utility functions for decorators
211 267 def numpy_not_available():
212 268 """Can numpy be imported? Returns true if numpy does NOT import.
213 269
214 270 This is used to make a decorator to skip tests that require numpy to be
215 271 available, but delay the 'import numpy' to test execution time.
216 272 """
217 273 try:
218 274 import numpy
219 275 np_not_avail = False
220 276 except ImportError:
221 277 np_not_avail = True
222 278
223 279 return np_not_avail
224 280
225 281 #-----------------------------------------------------------------------------
226 282 # Decorators for public use
227 283
228 284 skip_doctest = make_label_dec('skip_doctest',
229 285 """Decorator - mark a function or method for skipping its doctest.
230 286
231 287 This decorator allows you to mark a function whose docstring you wish to
232 288 omit from testing, while preserving the docstring for introspection, help,
233 289 etc.""")
234 290
235 291 # Decorators to skip certain tests on specific platforms.
236 292 skip_win32 = skipif(sys.platform == 'win32',
237 293 "This test does not run under Windows")
238 294 skip_linux = skipif(sys.platform == 'linux2',
239 295 "This test does not run under Linux")
240 296 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
241 297
242 298
243 299 # Decorators to skip tests if not on specific platforms.
244 300 skip_if_not_win32 = skipif(sys.platform != 'win32',
245 301 "This test only runs under Windows")
246 302 skip_if_not_linux = skipif(sys.platform != 'linux2',
247 303 "This test only runs under Linux")
248 304 skip_if_not_osx = skipif(sys.platform != 'darwin',
249 305 "This test only runs under OSX")
250 306
251 307 # Other skip decorators
252 308 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
253 309
254 310 skipknownfailure = skip('This test is known to fail')
@@ -1,324 +1,322 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 #-----------------------------------------------------------------------------
20 20 # Module imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import os.path as path
25 25 import sys
26 26 import subprocess
27 27 import tempfile
28 28 import time
29 29 import warnings
30 30
31 31 import nose.plugins.builtin
32 32 from nose.core import TestProgram
33 33
34 34 from IPython.utils.platutils import find_cmd
35 35 # from IPython.testing.plugin.ipdoctest import IPythonDoctest
36 36
37 37 pjoin = path.join
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Logic for skipping doctests
41 41 #-----------------------------------------------------------------------------
42 42
43 43 def test_for(mod):
44 44 """Test to see if mod is importable."""
45 45 try:
46 46 __import__(mod)
47 47 except ImportError:
48 48 return False
49 49 else:
50 50 return True
51 51
52 52 have_curses = test_for('_curses')
53 53 have_wx = test_for('wx')
54 54 have_wx_aui = test_for('wx.aui')
55 55 have_zi = test_for('zope.interface')
56 56 have_twisted = test_for('twisted')
57 57 have_foolscap = test_for('foolscap')
58 58 have_objc = test_for('objc')
59 59 have_pexpect = test_for('pexpect')
60 60 have_gtk = test_for('gtk')
61 61 have_gobject = test_for('gobject')
62 62
63 63
64 64 def make_exclude():
65 65
66 66 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
67 67 # testing problems. We should strive to minimize the number of skipped
68 68 # modules, since this means untested code. As the testing machinery
69 69 # solidifies, this list should eventually become empty.
70 70 EXCLUDE = [pjoin('IPython', 'external'),
71 71 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
72 72 pjoin('IPython_doctest_plugin'),
73 73 pjoin('IPython', 'quarantine'),
74 74 pjoin('IPython', 'deathrow'),
75 75 pjoin('IPython', 'testing', 'attic'),
76 76 pjoin('IPython', 'testing', 'tools'),
77 77 pjoin('IPython', 'testing', 'mkdoctests'),
78 78 pjoin('IPython', 'lib', 'inputhook')
79 79 ]
80 80
81 81 if not have_wx:
82 82 EXCLUDE.append(pjoin('IPython', 'gui'))
83 83 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
84 84 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookwx'))
85 85
86 86 if not have_gtk or not have_gobject:
87 87 EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookgtk'))
88 88
89 89 if not have_wx_aui:
90 90 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
91 91
92 92 if not have_objc:
93 93 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
94 94
95 95 if not sys.platform == 'win32':
96 96 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32'))
97 97
98 98 # These have to be skipped on win32 because the use echo, rm, cd, etc.
99 99 # See ticket https://bugs.launchpad.net/bugs/366982
100 100 if sys.platform == 'win32':
101 101 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
102 102 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
103 103
104 104 if not os.name == 'posix':
105 105 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix'))
106 106
107 107 if not have_pexpect:
108 108 EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner'))
109 109
110 110 # This is scary. We still have things in frontend and testing that
111 111 # are being tested by nose that use twisted. We need to rethink
112 112 # how we are isolating dependencies in testing.
113 113 if not (have_twisted and have_zi and have_foolscap):
114 114 EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
115 115 EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
116 116 EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase'))
117 117 EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
118 118 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
119 119 'test_linefrontend'))
120 120 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
121 121 'test_frontendbase'))
122 122 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
123 123 'test_prefilterfrontend'))
124 124 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
125 125 'test_asyncfrontendbase')),
126 126 EXCLUDE.append(pjoin('IPython', 'testing', 'parametric'))
127 127 EXCLUDE.append(pjoin('IPython', 'testing', 'util'))
128 EXCLUDE.append(pjoin('IPython', 'testing', 'tests',
129 'test_decorators_trial'))
130 128
131 129 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
132 130 if sys.platform == 'win32':
133 131 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
134 132
135 133 return EXCLUDE
136 134
137 135
138 136 #-----------------------------------------------------------------------------
139 137 # Functions and classes
140 138 #-----------------------------------------------------------------------------
141 139
142 140 def run_iptest():
143 141 """Run the IPython test suite using nose.
144 142
145 143 This function is called when this script is **not** called with the form
146 144 `iptest all`. It simply calls nose with appropriate command line flags
147 145 and accepts all of the standard nose arguments.
148 146 """
149 147
150 148 warnings.filterwarnings('ignore',
151 149 'This will be removed soon. Use IPython.testing.util instead')
152 150
153 151 argv = sys.argv + [
154 152 # Loading ipdoctest causes problems with Twisted.
155 153 # I am removing this as a temporary fix to get the
156 154 # test suite back into working shape. Our nose
157 155 # plugin needs to be gone through with a fine
158 156 # toothed comb to find what is causing the problem.
159 157 # '--with-ipdoctest',
160 158 # '--ipdoctest-tests','--ipdoctest-extension=txt',
161 159 # '--detailed-errors',
162 160
163 161 # We add --exe because of setuptools' imbecility (it
164 162 # blindly does chmod +x on ALL files). Nose does the
165 163 # right thing and it tries to avoid executables,
166 164 # setuptools unfortunately forces our hand here. This
167 165 # has been discussed on the distutils list and the
168 166 # setuptools devs refuse to fix this problem!
169 167 '--exe',
170 168 ]
171 169
172 170 # Detect if any tests were required by explicitly calling an IPython
173 171 # submodule or giving a specific path
174 172 has_tests = False
175 173 for arg in sys.argv:
176 174 if 'IPython' in arg or arg.endswith('.py') or \
177 175 (':' in arg and '.py' in arg):
178 176 has_tests = True
179 177 break
180 178
181 179 # If nothing was specifically requested, test full IPython
182 180 if not has_tests:
183 181 argv.append('IPython')
184 182
185 183 # Construct list of plugins, omitting the existing doctest plugin, which
186 184 # ours replaces (and extends).
187 185 EXCLUDE = make_exclude()
188 186 plugins = []
189 187 # plugins = [IPythonDoctest(EXCLUDE)]
190 188 for p in nose.plugins.builtin.plugins:
191 189 plug = p()
192 190 if plug.name == 'doctest':
193 191 continue
194 192 plugins.append(plug)
195 193
196 194 TestProgram(argv=argv,plugins=plugins)
197 195
198 196
199 197 class IPTester(object):
200 198 """Call that calls iptest or trial in a subprocess.
201 199 """
202 200 def __init__(self,runner='iptest',params=None):
203 201 """ """
204 202 if runner == 'iptest':
205 203 self.runner = ['iptest','-v']
206 204 else:
207 205 self.runner = [find_cmd('trial')]
208 206 if params is None:
209 207 params = []
210 208 if isinstance(params,str):
211 209 params = [params]
212 210 self.params = params
213 211
214 212 # Assemble call
215 213 self.call_args = self.runner+self.params
216 214
217 215 if sys.platform == 'win32':
218 216 def run(self):
219 217 """Run the stored commands"""
220 218 # On Windows, cd to temporary directory to run tests. Otherwise,
221 219 # Twisted's trial may not be able to execute 'trial IPython', since
222 220 # it will confuse the IPython module name with the ipython
223 221 # execution scripts, because the windows file system isn't case
224 222 # sensitive.
225 223 # We also use os.system instead of subprocess.call, because I was
226 224 # having problems with subprocess and I just don't know enough
227 225 # about win32 to debug this reliably. Os.system may be the 'old
228 226 # fashioned' way to do it, but it works just fine. If someone
229 227 # later can clean this up that's fine, as long as the tests run
230 228 # reliably in win32.
231 229 curdir = os.getcwd()
232 230 os.chdir(tempfile.gettempdir())
233 231 stat = os.system(' '.join(self.call_args))
234 232 os.chdir(curdir)
235 233 return stat
236 234 else:
237 235 def run(self):
238 236 """Run the stored commands"""
239 237 return subprocess.call(self.call_args)
240 238
241 239
242 240 def make_runners():
243 241 """Define the top-level packages that need to be tested.
244 242 """
245 243
246 244 nose_packages = ['config', 'core', 'extensions',
247 245 'frontend', 'lib',
248 246 'scripts', 'testing', 'utils']
249 247 trial_packages = ['kernel']
250 248
251 249 if have_wx:
252 250 nose_packages.append('gui')
253 251
254 252 nose_packages = ['IPython.%s' % m for m in nose_packages ]
255 253 trial_packages = ['IPython.%s' % m for m in trial_packages ]
256 254
257 255 # Make runners
258 256 runners = dict()
259 257
260 258 nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages]))
261 259 if have_zi and have_twisted and have_foolscap:
262 260 trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages]))
263 261 runners.update(nose_runners)
264 262 runners.update(trial_runners)
265 263
266 264 return runners
267 265
268 266
269 267 def run_iptestall():
270 268 """Run the entire IPython test suite by calling nose and trial.
271 269
272 270 This function constructs :class:`IPTester` instances for all IPython
273 271 modules and package and then runs each of them. This causes the modules
274 272 and packages of IPython to be tested each in their own subprocess using
275 273 nose or twisted.trial appropriately.
276 274 """
277 275
278 276 runners = make_runners()
279 277
280 278 # Run all test runners, tracking execution time
281 279 failed = {}
282 280 t_start = time.time()
283 281 for name,runner in runners.iteritems():
284 282 print '*'*77
285 283 print 'IPython test group:',name
286 284 res = runner.run()
287 285 if res:
288 286 failed[name] = res
289 287 t_end = time.time()
290 288 t_tests = t_end - t_start
291 289 nrunners = len(runners)
292 290 nfail = len(failed)
293 291 # summarize results
294 292 print
295 293 print '*'*77
296 294 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
297 295 print
298 296 if not failed:
299 297 print 'OK'
300 298 else:
301 299 # If anything went wrong, point out what command to rerun manually to
302 300 # see the actual errors and individual summary
303 301 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
304 302 for name in failed:
305 303 failed_runner = runners[name]
306 304 print '-'*40
307 305 print 'Runner failed:',name
308 306 print 'You may wish to rerun this one individually, with:'
309 307 print ' '.join(failed_runner.call_args)
310 308 print
311 309
312 310
313 311 def main():
314 312 if len(sys.argv) == 1:
315 313 run_iptestall()
316 314 else:
317 315 if sys.argv[1] == 'all':
318 316 run_iptestall()
319 317 else:
320 318 run_iptest()
321 319
322 320
323 321 if __name__ == '__main__':
324 322 main()
@@ -1,55 +1,58 b''
1 1 """Parametric testing on top of twisted.trial.unittest.
2 2
3 XXX - It may be possbile to deprecate this in favor of the new, cleaner
4 parametric code. We just need to double-check that the new code doesn't clash
5 with Twisted (we know it works with nose and unittest).
3 6 """
4 7
5 8 __all__ = ['parametric','Parametric']
6 9
7 10 from twisted.trial.unittest import TestCase
8 11
9 12 def partial(f, *partial_args, **partial_kwargs):
10 13 """Generate a partial class method.
11 14
12 15 """
13 16 def partial_func(self, *args, **kwargs):
14 17 dikt = dict(kwargs)
15 18 dikt.update(partial_kwargs)
16 19 return f(self, *(partial_args+args), **dikt)
17 20
18 21 return partial_func
19 22
20 23 def parametric(f):
21 24 """Mark f as a parametric test.
22 25
23 26 """
24 27 f._parametric = True
25 28 return classmethod(f)
26 29
27 30 def Parametric(cls):
28 31 """Register parametric tests with a class.
29 32
30 33 """
31 34 # Walk over all tests marked with @parametric
32 35 test_generators = [getattr(cls,f) for f in dir(cls)
33 36 if f.startswith('test')]
34 37 test_generators = [m for m in test_generators if hasattr(m,'_parametric')]
35 38 for test_gen in test_generators:
36 39 test_name = test_gen.func_name
37 40
38 41 # Insert a new test for each parameter
39 42 for n,test_and_params in enumerate(test_gen()):
40 43 test_method = test_and_params[0]
41 44 test_params = test_and_params[1:]
42 45
43 46 # Here we use partial (defined above), which returns a
44 47 # class method of type ``types.FunctionType``, unlike
45 48 # functools.partial which returns a function of type
46 49 # ``functools.partial``.
47 50 partial_func = partial(test_method,*test_params)
48 51 # rename the test to look like a testcase
49 52 partial_func.__name__ = 'test_' + partial_func.__name__
50 53
51 54 # insert the new function into the class as a test
52 55 setattr(cls, test_name + '_%s' % n, partial_func)
53 56
54 57 # rename test generator so it isn't called again by nose
55 58 test_gen.im_func.func_name = '__done_' + test_name
@@ -1,161 +1,219 b''
1 1 """Tests for the decorators we've created for IPython.
2 2 """
3 3
4 4 # Module imports
5 5 # Std lib
6 6 import inspect
7 7 import sys
8 import unittest
8 9
9 10 # Third party
10 11 import nose.tools as nt
11 12
12 13 # Our own
13 14 from IPython.testing import decorators as dec
14
15 from IPython.testing.ipunittest import ParametricTestCase
15 16
16 17 #-----------------------------------------------------------------------------
17 18 # Utilities
18 19
19 20 # Note: copied from OInspect, kept here so the testing stuff doesn't create
20 21 # circular dependencies and is easier to reuse.
21 22 def getargspec(obj):
22 23 """Get the names and default values of a function's arguments.
23 24
24 25 A tuple of four things is returned: (args, varargs, varkw, defaults).
25 26 'args' is a list of the argument names (it may contain nested lists).
26 27 'varargs' and 'varkw' are the names of the * and ** arguments or None.
27 28 'defaults' is an n-tuple of the default values of the last n arguments.
28 29
29 30 Modified version of inspect.getargspec from the Python Standard
30 31 Library."""
31 32
32 33 if inspect.isfunction(obj):
33 34 func_obj = obj
34 35 elif inspect.ismethod(obj):
35 36 func_obj = obj.im_func
36 37 else:
37 38 raise TypeError, 'arg is not a Python function'
38 39 args, varargs, varkw = inspect.getargs(func_obj.func_code)
39 40 return args, varargs, varkw, func_obj.func_defaults
40 41
41 42 #-----------------------------------------------------------------------------
42 43 # Testing functions
43 44
45 @dec.as_unittest
46 def trivial():
47 """A trivial test"""
48 pass
49
50 # Some examples of parametric tests.
51
52 def is_smaller(i,j):
53 assert i<j,"%s !< %s" % (i,j)
54
55 class Tester(ParametricTestCase):
56
57 def test_parametric(self):
58 yield is_smaller(3, 4)
59 x, y = 1, 2
60 yield is_smaller(x, y)
61
62 @dec.parametric
63 def test_par_standalone():
64 yield is_smaller(3, 4)
65 x, y = 1, 2
66 yield is_smaller(x, y)
67
68
44 69 @dec.skip
45 70 def test_deliberately_broken():
46 71 """A deliberately broken test - we want to skip this one."""
47 72 1/0
48 73
49 74 @dec.skip('Testing the skip decorator')
50 75 def test_deliberately_broken2():
51 76 """Another deliberately broken test - we want to skip this one."""
52 77 1/0
53 78
54 79
55 80 # Verify that we can correctly skip the doctest for a function at will, but
56 81 # that the docstring itself is NOT destroyed by the decorator.
57 82 @dec.skip_doctest
58 83 def doctest_bad(x,y=1,**k):
59 84 """A function whose doctest we need to skip.
60 85
61 86 >>> 1+1
62 87 3
63 88 """
64 89 print 'x:',x
65 90 print 'y:',y
66 91 print 'k:',k
67 92
68 93
69 94 def call_doctest_bad():
70 95 """Check that we can still call the decorated functions.
71 96
72 97 >>> doctest_bad(3,y=4)
73 98 x: 3
74 99 y: 4
75 100 k: {}
76 101 """
77 102 pass
78 103
79 104
80 105 def test_skip_dt_decorator():
81 106 """Doctest-skipping decorator should preserve the docstring.
82 107 """
83 108 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
84 109 check = """A function whose doctest we need to skip.
85 110
86 111 >>> 1+1
87 112 3
88 113 """
89 114 # Fetch the docstring from doctest_bad after decoration.
90 115 val = doctest_bad.__doc__
91 116
92 117 assert check==val,"doctest_bad docstrings don't match"
93 118
94 119 # Doctest skipping should work for class methods too
95 120 class foo(object):
96 121 """Foo
97 122
98 123 Example:
99 124
100 125 >>> 1+1
101 126 2
102 127 """
103 128
104 129 @dec.skip_doctest
105 130 def __init__(self,x):
106 131 """Make a foo.
107 132
108 133 Example:
109 134
110 135 >>> f = foo(3)
111 136 junk
112 137 """
113 138 print 'Making a foo.'
114 139 self.x = x
115 140
116 141 @dec.skip_doctest
117 142 def bar(self,y):
118 143 """Example:
119 144
120 145 >>> f = foo(3)
121 146 >>> f.bar(0)
122 147 boom!
123 148 >>> 1/0
124 149 bam!
125 150 """
126 151 return 1/y
127 152
128 153 def baz(self,y):
129 154 """Example:
130 155
131 156 >>> f = foo(3)
132 157 Making a foo.
133 158 >>> f.baz(3)
134 159 True
135 160 """
136 161 return self.x==y
137 162
138 163
139 164
140 165 def test_skip_dt_decorator2():
141 166 """Doctest-skipping decorator should preserve function signature.
142 167 """
143 168 # Hardcoded correct answer
144 169 dtargs = (['x', 'y'], None, 'k', (1,))
145 170 # Introspect out the value
146 171 dtargsr = getargspec(doctest_bad)
147 172 assert dtargsr==dtargs, \
148 173 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
149 174
150 175
151 176 @dec.skip_linux
152 177 def test_linux():
153 178 nt.assert_not_equals(sys.platform,'linux2',"This test can't run under linux")
154 179
155 180 @dec.skip_win32
156 181 def test_win32():
157 182 nt.assert_not_equals(sys.platform,'win32',"This test can't run under windows")
158 183
159 184 @dec.skip_osx
160 185 def test_osx():
161 186 nt.assert_not_equals(sys.platform,'darwin',"This test can't run under osx")
187
188
189 # Verify that the same decorators work for methods.
190 # Note: this code is identical to that in test_decorators_trial, but that one
191 # uses twisted's unittest, not the one from the stdlib, which we are using
192 # here. While somewhat redundant, we want to check both with the stdlib and
193 # with twisted, so the duplication is OK.
194 class TestDecoratorsTrial(unittest.TestCase):
195
196 @dec.skip()
197 def test_deliberately_broken(self):
198 """A deliberately broken test - we want to skip this one."""
199 1/0
200
201 @dec.skip('Testing the skip decorator')
202 def test_deliberately_broken2(self):
203 """Another deliberately broken test - we want to skip this one."""
204 1/0
205
206 @dec.skip_linux
207 def test_linux(self):
208 self.assertNotEquals(sys.platform, 'linux2',
209 "This test can't run under linux")
210
211 @dec.skip_win32
212 def test_win32(self):
213 self.assertNotEquals(sys.platform, 'win32',
214 "This test can't run under windows")
215
216 @dec.skip_osx
217 def test_osx(self):
218 self.assertNotEquals(sys.platform, 'darwin',
219 "This test can't run under osx")
@@ -1,133 +1,132 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
33 33 import nose.tools as nt
34 34
35 35 from IPython.utils import genutils
36 from IPython.testing import decorators as dec
37 36
38 37 #-----------------------------------------------------------------------------
39 38 # Globals
40 39 #-----------------------------------------------------------------------------
41 40
42 41 # Make a bunch of nose.tools assert wrappers that can be used in test
43 42 # generators. This will expose an assert* function for each one in nose.tools.
44 43
45 44 _tpl = """
46 45 def %(name)s(*a,**kw):
47 46 return nt.%(name)s(*a,**kw)
48 47 """
49 48
50 49 for _x in [a for a in dir(nt) if a.startswith('assert')]:
51 50 exec _tpl % dict(name=_x)
52 51
53 52 #-----------------------------------------------------------------------------
54 53 # Functions and classes
55 54 #-----------------------------------------------------------------------------
56 55
57 56
58 57 def full_path(startPath,files):
59 58 """Make full paths for all the listed files, based on startPath.
60 59
61 60 Only the base part of startPath is kept, since this routine is typically
62 61 used with a script's __file__ variable as startPath. The base of startPath
63 62 is then prepended to all the listed files, forming the output list.
64 63
65 64 Parameters
66 65 ----------
67 66 startPath : string
68 67 Initial path to use as the base for the results. This path is split
69 68 using os.path.split() and only its first component is kept.
70 69
71 70 files : string or list
72 71 One or more files.
73 72
74 73 Examples
75 74 --------
76 75
77 76 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 77 ['/foo/a.txt', '/foo/b.txt']
79 78
80 79 >>> full_path('/foo',['a.txt','b.txt'])
81 80 ['/a.txt', '/b.txt']
82 81
83 82 If a single file is given, the output is still a list:
84 83 >>> full_path('/foo','a.txt')
85 84 ['/a.txt']
86 85 """
87 86
88 87 files = genutils.list_strings(files)
89 88 base = os.path.split(startPath)[0]
90 89 return [ os.path.join(base,f) for f in files ]
91 90
92 91
93 92 def parse_test_output(txt):
94 93 """Parse the output of a test run and return errors, failures.
95 94
96 95 Parameters
97 96 ----------
98 97 txt : str
99 98 Text output of a test run, assumed to contain a line of one of the
100 99 following forms::
101 100 'FAILED (errors=1)'
102 101 'FAILED (failures=1)'
103 102 'FAILED (errors=1, failures=1)'
104 103
105 104 Returns
106 105 -------
107 106 nerr, nfail: number of errors and failures.
108 107 """
109 108
110 109 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
111 110 if err_m:
112 111 nerr = int(err_m.group(1))
113 112 nfail = 0
114 113 return nerr, nfail
115 114
116 115 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
117 116 if fail_m:
118 117 nerr = 0
119 118 nfail = int(fail_m.group(1))
120 119 return nerr, nfail
121 120
122 121 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
123 122 re.MULTILINE)
124 123 if both_m:
125 124 nerr = int(both_m.group(1))
126 125 nfail = int(both_m.group(2))
127 126 return nerr, nfail
128 127
129 128 # If the input didn't match any of these forms, assume no error/failures
130 129 return 0, 0
131 130
132 131 # So nose doesn't think this is a test
133 132 parse_test_output.__test__ = False
General Comments 0
You need to be logged in to leave comments. Login now