##// END OF EJS Templates
Terminology: output is silenced, not disabled.
Terminology: output is silenced, not disabled.

File last commit:

r25384:a67a3d70
r27907:4c580c14
Show More
ipunittest.py
178 lines | 6.4 KiB | text/x-python | PythonLexer
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 """Experimental code for cleaner support of IPython syntax with unittest.
In IPython up until 0.10, we've used very hacked up nose machinery for running
tests with IPython special syntax, and this has proved to be extremely slow.
This module provides decorators to try a different approach, stemming from a
conversation Brian and I (FP) had about this problem Sept/09.
The goal is to be able to easily write simple functions that can be seen by
unittest as tests, and ultimately for these to support doctests with full
IPython syntax. Nose already offers this based on naming conventions and our
hackish plugins, but we are seeking to move away from nose dependencies if
possible.
This module follows a different approach, based on decorators.
- A decorator called @ipdoctest can mark any function as having a docstring
that should be viewed as a doctest, but after syntax conversion.
Authors
-------
- Fernando Perez <Fernando.Perez@berkeley.edu>
"""
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 #-----------------------------------------------------------------------------
Matthias BUSSONNIER
update copyright to 2011/20xx-2011...
r5390 # Copyright (C) 2009-2011 The IPython Development Team
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 #
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Stdlib
import re
import unittest
Fernando Perez
Remove code that is not needed once python 2.6 is our minimal version.
r3298 from doctest import DocTestFinder, DocTestRunner, TestResults
Matthias Bussonnier
Also run test with Pytest....
r25117 from IPython.terminal.interactiveshell import InteractiveShell
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368
#-----------------------------------------------------------------------------
# Classes and functions
#-----------------------------------------------------------------------------
def count_failures(runner):
"""Count number of failures in a doctest runner.
Code modeled after the summarize() method in doctest.
"""
return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
class IPython2PythonConverter(object):
"""Convert IPython 'syntax' to valid Python.
Eventually this code may grow to be the full IPython syntax conversion
luzpaz
Misc. typo fixes...
r24084 implementation, but for now it only does prompt conversion."""
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368
def __init__(self):
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 self.rps1 = re.compile(r'In\ \[\d+\]: ')
self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
self.pyps1 = '>>> '
self.pyps2 = '... '
Matthias Bussonnier
fix some other syntax warnings
r24780 self.rpyps1 = re.compile (r'(\s*%s)(.*)$' % self.pyps1)
self.rpyps2 = re.compile (r'(\s*%s)(.*)$' % self.pyps2)
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368
def __call__(self, ds):
"""Convert IPython prompts to python ones in a string."""
Fernando Perez
Fixes for test suite in win32 when all dependencies (esp. Twisted) are...
r2461 from . import globalipapp
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 pyps1 = '>>> '
pyps2 = '... '
pyout = ''
dnew = ds
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414 dnew = self.rps1.sub(pyps1, dnew)
dnew = self.rps2.sub(pyps2, dnew)
dnew = self.rout.sub(pyout, dnew)
Matthias Bussonnier
Also run test with Pytest....
r25117 ip = InteractiveShell.instance()
Fernando Perez
Massive amount of work to improve the test suite, restores doctests....
r2414
# Convert input IPython source into valid Python.
out = []
newline = out.append
for line in dnew.splitlines():
mps1 = self.rpyps1.match(line)
if mps1 is not None:
prompt, text = mps1.groups()
newline(prompt+ip.prefilter(text, False))
continue
mps2 = self.rpyps2.match(line)
if mps2 is not None:
prompt, text = mps2.groups()
newline(prompt+ip.prefilter(text, True))
continue
newline(line)
newline('') # ensure a closing newline, needed by doctest
#print "PYSRC:", '\n'.join(out) # dbg
return '\n'.join(out)
#return dnew
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368
class Doc2UnitTester(object):
"""Class whose instances act as a decorator for docstring testing.
In practice we're only likely to need one instance ever, made below (though
no attempt is made at turning it into a singleton, there is no need for
that).
"""
def __init__(self, verbose=False):
"""New decorator.
Parameters
----------
verbose : boolean, optional (False)
Passed to the doctest finder and runner to control verbosity.
"""
self.verbose = verbose
# We can reuse the same finder for all instances
self.finder = DocTestFinder(verbose=verbose, recurse=False)
def __call__(self, func):
"""Use as a decorator: doctest a function's docstring as a unittest.
This version runs normal doctests, but the idea is to make it later run
ipython syntax instead."""
# Capture the enclosing instance with a different name, so the new
# class below can see it without confusion regarding its own 'self'
# that will point to the test instance at runtime
d2u = self
# Rewrite the function's docstring to have python syntax
if func.__doc__ is not None:
func.__doc__ = ip2py(func.__doc__)
# Now, create a tester object that is a real unittest instance, so
# normal unittest machinery (or Nose, or Trial) can find it.
class Tester(unittest.TestCase):
def test(self):
# Make a new runner per function to be tested
runner = DocTestRunner(verbose=d2u.verbose)
Matthias Bussonnier
Actually run some tests....
r24503 for the_test in d2u.finder.find(func, func.__name__):
runner.run(the_test)
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 failed = count_failures(runner)
if failed:
# Since we only looked at a single function's docstring,
# failed should contain at most one item. More than that
# is a case we can't handle and should error out on
if len(failed) > 1:
Marc Hernandez Cabot
fix argument in format string
r25384 err = "Invalid number of test results: %s" % failed
Fernando Perez
Add new testing support machinery with better parametric tests....
r2368 raise ValueError(err)
# Report a normal failure.
self.fail('failed doctests: %s' % str(failed[0]))
# Rename it so test reports have the original signature.
Tester.__name__ = func.__name__
return Tester
def ipdocstring(func):
"""Change the function docstring via ip2py.
"""
if func.__doc__ is not None:
func.__doc__ = ip2py(func.__doc__)
return func
# Make an instance of the classes for public use
ipdoctest = Doc2UnitTester()
ip2py = IPython2PythonConverter()