This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/packages/python/yap_kernel/yap_ipython/testing/ipunittest.py

177 lines
6.3 KiB
Python
Raw Normal View History

2018-01-05 16:57:38 +00:00
"""Experimental code for cleaner support of yap_ipython syntax with unittest.
In yap_ipython up until 0.10, we've used very hacked up nose machinery for running
tests with yap_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
yap_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>
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2009-2011 The yap_ipython Development Team
#
# 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
from doctest import DocTestFinder, DocTestRunner, TestResults
#-----------------------------------------------------------------------------
# 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 yap_ipython 'syntax' to valid Python.
Eventually this code may grow to be the full yap_ipython syntax conversion
implementation, but for now it only does prompt conversion."""
def __init__(self):
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 = '... '
self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
def __call__(self, ds):
"""Convert yap_ipython prompts to python ones in a string."""
from . import globalipapp
pyps1 = '>>> '
pyps2 = '... '
pyout = ''
dnew = ds
dnew = self.rps1.sub(pyps1, dnew)
dnew = self.rps2.sub(pyps2, dnew)
dnew = self.rout.sub(pyout, dnew)
ip = globalipapp.get_ipython()
# Convert input yap_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
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)
map(runner.run, d2u.finder.find(func, func.__name__))
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:
err = "Invalid number of test results:" % failed
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()