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