##// END OF EJS Templates
Remove code that is not needed once python 2.6 is our minimal version.
Fernando Perez -
Show More
@@ -1,188 +1,184 b''
1 """Experimental code for cleaner support of IPython syntax with unittest.
1 """Experimental code for cleaner support of IPython syntax with unittest.
2
2
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
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.
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
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.
6 conversation Brian and I (FP) had about this problem Sept/09.
7
7
8 The goal is to be able to easily write simple functions that can be seen by
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
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
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
11 hackish plugins, but we are seeking to move away from nose dependencies if
12 possible.
12 possible.
13
13
14 This module follows a different approach, based on decorators.
14 This module follows a different approach, based on decorators.
15
15
16 - A decorator called @ipdoctest can mark any function as having a docstring
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.
17 that should be viewed as a doctest, but after syntax conversion.
18
18
19 Authors
19 Authors
20 -------
20 -------
21
21
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 """
23 """
24
24
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Copyright (C) 2009 The IPython Development Team
28 # Copyright (C) 2009 The IPython Development Team
29 #
29 #
30 # Distributed under the terms of the BSD License. The full license is in
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
31 # the file COPYING, distributed as part of this software.
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Imports
35 # Imports
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 # Stdlib
38 # Stdlib
39 import re
39 import re
40 import sys
40 import sys
41 import unittest
41 import unittest
42 from doctest import DocTestFinder, DocTestRunner
42 from doctest import DocTestFinder, DocTestRunner, TestResults
43 try:
44 from doctest import TestResults
45 except:
46 from ._doctest26 import TestResults
47
43
48 # We already have python3-compliant code for parametric tests
44 # We already have python3-compliant code for parametric tests
49 if sys.version[0]=='2':
45 if sys.version[0]=='2':
50 from ._paramtestpy2 import ParametricTestCase
46 from ._paramtestpy2 import ParametricTestCase
51 else:
47 else:
52 from ._paramtestpy3 import ParametricTestCase
48 from ._paramtestpy3 import ParametricTestCase
53
49
54 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
55 # Classes and functions
51 # Classes and functions
56 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
57
53
58 def count_failures(runner):
54 def count_failures(runner):
59 """Count number of failures in a doctest runner.
55 """Count number of failures in a doctest runner.
60
56
61 Code modeled after the summarize() method in doctest.
57 Code modeled after the summarize() method in doctest.
62 """
58 """
63 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
59 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
64
60
65
61
66 class IPython2PythonConverter(object):
62 class IPython2PythonConverter(object):
67 """Convert IPython 'syntax' to valid Python.
63 """Convert IPython 'syntax' to valid Python.
68
64
69 Eventually this code may grow to be the full IPython syntax conversion
65 Eventually this code may grow to be the full IPython syntax conversion
70 implementation, but for now it only does prompt convertion."""
66 implementation, but for now it only does prompt convertion."""
71
67
72 def __init__(self):
68 def __init__(self):
73 self.rps1 = re.compile(r'In\ \[\d+\]: ')
69 self.rps1 = re.compile(r'In\ \[\d+\]: ')
74 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
70 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
75 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
71 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
76 self.pyps1 = '>>> '
72 self.pyps1 = '>>> '
77 self.pyps2 = '... '
73 self.pyps2 = '... '
78 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
74 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
79 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
75 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
80
76
81 def __call__(self, ds):
77 def __call__(self, ds):
82 """Convert IPython prompts to python ones in a string."""
78 """Convert IPython prompts to python ones in a string."""
83 from . import globalipapp
79 from . import globalipapp
84
80
85 pyps1 = '>>> '
81 pyps1 = '>>> '
86 pyps2 = '... '
82 pyps2 = '... '
87 pyout = ''
83 pyout = ''
88
84
89 dnew = ds
85 dnew = ds
90 dnew = self.rps1.sub(pyps1, dnew)
86 dnew = self.rps1.sub(pyps1, dnew)
91 dnew = self.rps2.sub(pyps2, dnew)
87 dnew = self.rps2.sub(pyps2, dnew)
92 dnew = self.rout.sub(pyout, dnew)
88 dnew = self.rout.sub(pyout, dnew)
93 ip = globalipapp.get_ipython()
89 ip = globalipapp.get_ipython()
94
90
95 # Convert input IPython source into valid Python.
91 # Convert input IPython source into valid Python.
96 out = []
92 out = []
97 newline = out.append
93 newline = out.append
98 for line in dnew.splitlines():
94 for line in dnew.splitlines():
99
95
100 mps1 = self.rpyps1.match(line)
96 mps1 = self.rpyps1.match(line)
101 if mps1 is not None:
97 if mps1 is not None:
102 prompt, text = mps1.groups()
98 prompt, text = mps1.groups()
103 newline(prompt+ip.prefilter(text, False))
99 newline(prompt+ip.prefilter(text, False))
104 continue
100 continue
105
101
106 mps2 = self.rpyps2.match(line)
102 mps2 = self.rpyps2.match(line)
107 if mps2 is not None:
103 if mps2 is not None:
108 prompt, text = mps2.groups()
104 prompt, text = mps2.groups()
109 newline(prompt+ip.prefilter(text, True))
105 newline(prompt+ip.prefilter(text, True))
110 continue
106 continue
111
107
112 newline(line)
108 newline(line)
113 newline('') # ensure a closing newline, needed by doctest
109 newline('') # ensure a closing newline, needed by doctest
114 #print "PYSRC:", '\n'.join(out) # dbg
110 #print "PYSRC:", '\n'.join(out) # dbg
115 return '\n'.join(out)
111 return '\n'.join(out)
116
112
117 #return dnew
113 #return dnew
118
114
119
115
120 class Doc2UnitTester(object):
116 class Doc2UnitTester(object):
121 """Class whose instances act as a decorator for docstring testing.
117 """Class whose instances act as a decorator for docstring testing.
122
118
123 In practice we're only likely to need one instance ever, made below (though
119 In practice we're only likely to need one instance ever, made below (though
124 no attempt is made at turning it into a singleton, there is no need for
120 no attempt is made at turning it into a singleton, there is no need for
125 that).
121 that).
126 """
122 """
127 def __init__(self, verbose=False):
123 def __init__(self, verbose=False):
128 """New decorator.
124 """New decorator.
129
125
130 Parameters
126 Parameters
131 ----------
127 ----------
132
128
133 verbose : boolean, optional (False)
129 verbose : boolean, optional (False)
134 Passed to the doctest finder and runner to control verbosity.
130 Passed to the doctest finder and runner to control verbosity.
135 """
131 """
136 self.verbose = verbose
132 self.verbose = verbose
137 # We can reuse the same finder for all instances
133 # We can reuse the same finder for all instances
138 self.finder = DocTestFinder(verbose=verbose, recurse=False)
134 self.finder = DocTestFinder(verbose=verbose, recurse=False)
139
135
140 def __call__(self, func):
136 def __call__(self, func):
141 """Use as a decorator: doctest a function's docstring as a unittest.
137 """Use as a decorator: doctest a function's docstring as a unittest.
142
138
143 This version runs normal doctests, but the idea is to make it later run
139 This version runs normal doctests, but the idea is to make it later run
144 ipython syntax instead."""
140 ipython syntax instead."""
145
141
146 # Capture the enclosing instance with a different name, so the new
142 # Capture the enclosing instance with a different name, so the new
147 # class below can see it without confusion regarding its own 'self'
143 # class below can see it without confusion regarding its own 'self'
148 # that will point to the test instance at runtime
144 # that will point to the test instance at runtime
149 d2u = self
145 d2u = self
150
146
151 # Rewrite the function's docstring to have python syntax
147 # Rewrite the function's docstring to have python syntax
152 if func.__doc__ is not None:
148 if func.__doc__ is not None:
153 func.__doc__ = ip2py(func.__doc__)
149 func.__doc__ = ip2py(func.__doc__)
154
150
155 # Now, create a tester object that is a real unittest instance, so
151 # Now, create a tester object that is a real unittest instance, so
156 # normal unittest machinery (or Nose, or Trial) can find it.
152 # normal unittest machinery (or Nose, or Trial) can find it.
157 class Tester(unittest.TestCase):
153 class Tester(unittest.TestCase):
158 def test(self):
154 def test(self):
159 # Make a new runner per function to be tested
155 # Make a new runner per function to be tested
160 runner = DocTestRunner(verbose=d2u.verbose)
156 runner = DocTestRunner(verbose=d2u.verbose)
161 map(runner.run, d2u.finder.find(func, func.__name__))
157 map(runner.run, d2u.finder.find(func, func.__name__))
162 failed = count_failures(runner)
158 failed = count_failures(runner)
163 if failed:
159 if failed:
164 # Since we only looked at a single function's docstring,
160 # Since we only looked at a single function's docstring,
165 # failed should contain at most one item. More than that
161 # failed should contain at most one item. More than that
166 # is a case we can't handle and should error out on
162 # is a case we can't handle and should error out on
167 if len(failed) > 1:
163 if len(failed) > 1:
168 err = "Invalid number of test results:" % failed
164 err = "Invalid number of test results:" % failed
169 raise ValueError(err)
165 raise ValueError(err)
170 # Report a normal failure.
166 # Report a normal failure.
171 self.fail('failed doctests: %s' % str(failed[0]))
167 self.fail('failed doctests: %s' % str(failed[0]))
172
168
173 # Rename it so test reports have the original signature.
169 # Rename it so test reports have the original signature.
174 Tester.__name__ = func.__name__
170 Tester.__name__ = func.__name__
175 return Tester
171 return Tester
176
172
177
173
178 def ipdocstring(func):
174 def ipdocstring(func):
179 """Change the function docstring via ip2py.
175 """Change the function docstring via ip2py.
180 """
176 """
181 if func.__doc__ is not None:
177 if func.__doc__ is not None:
182 func.__doc__ = ip2py(func.__doc__)
178 func.__doc__ = ip2py(func.__doc__)
183 return func
179 return func
184
180
185
181
186 # Make an instance of the classes for public use
182 # Make an instance of the classes for public use
187 ipdoctest = Doc2UnitTester()
183 ipdoctest = Doc2UnitTester()
188 ip2py = IPython2PythonConverter()
184 ip2py = IPython2PythonConverter()
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now