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