Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 277cf87

Browse files
committed
Add new testing support machinery with better parametric tests.
Also included are new tools for doctests with ipython syntax.
1 parent d8925ba commit 277cf87

9 files changed

Lines changed: 409 additions & 18 deletions

File tree

IPython/kernel/core/tests/test_redirectors.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121

2222
from twisted.trial import unittest
2323

24-
from IPython.testing import decorators_trial as dec
24+
from IPython.testing import decorators as dec
2525

2626
#-----------------------------------------------------------------------------
2727
# Tests
2828
#-----------------------------------------------------------------------------
2929

30-
3130
class TestRedirector(unittest.TestCase):
3231

3332
@dec.skip_win32

IPython/testing/decorators.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,79 @@
1010
your own tests. See the bottom of the file for the ready-made ones, and if you
1111
find yourself writing a new one that may be of generic use, add it here.
1212
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+
1327
NOTE: This file contains IPython-specific decorators and imports the
1428
numpy.testing.decorators file, which we've copied verbatim. Any of our own
1529
code will be added at the bottom if we end up extending this.
30+
31+
Authors
32+
-------
33+
34+
- Fernando Perez <[email protected]>
1635
"""
1736

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+
1848
# Stdlib imports
1949
import inspect
2050
import sys
51+
import unittest
2152

2253
# Third-party imports
2354

24-
# This is Michele Simionato's decorator module, also kept verbatim.
55+
# This is Michele Simionato's decorator module, kept verbatim.
2556
from IPython.external.decorator import decorator, update_wrapper
2657

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+
2767
# 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
29-
# of numpy.testing.decorators.
30-
from decorators_numpy import *
68+
# occasionally update from upstream: decorators.py is a copy of
69+
# numpy.testing.decorators, we expose all of it here.
70+
from IPython.external.decorators import *
71+
72+
#-----------------------------------------------------------------------------
73+
# Classes and functions
74+
#-----------------------------------------------------------------------------
3175

32-
##############################################################################
33-
# Local code begins
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()
82+
83+
Tester.__name__ = func.__name__
84+
85+
return Tester
3486

3587
# Utility functions
3688

@@ -51,21 +103,23 @@ def apply_wrapper(wrapper,func):
51103
def make_label_dec(label,ds=None):
52104
"""Factory function to create a decorator that applies one or more labels.
53105
54-
:Parameters:
106+
Parameters
107+
----------
55108
label : string or sequence
56109
One or more labels that will be applied by the decorator to the functions
57110
it decorates. Labels are attributes of the decorated function with their
58111
value set to True.
59112
60-
:Keywords:
61113
ds : string
62114
An optional docstring for the resulting decorator. If not given, a
63115
default docstring is auto-generated.
64116
65-
:Returns:
117+
Returns
118+
-------
66119
A decorator.
67120
68-
:Examples:
121+
Examples
122+
--------
69123
70124
A simple labeling decorator:
71125
>>> slow = make_label_dec('slow')
@@ -193,11 +247,13 @@ def skipper_gen(*args, **kwargs):
193247
def skip(msg=None):
194248
"""Decorator factory - mark a test function for skipping from test suite.
195249
196-
:Parameters:
250+
Parameters
251+
----------
197252
msg : string
198253
Optional message to be added.
199254
200-
:Returns:
255+
Returns
256+
-------
201257
decorator : function
202258
Decorator, which, when applied to a function, causes SkipTest
203259
to be raised, with the optional message added.

IPython/testing/iptest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ def make_exclude():
125125
'test_asyncfrontendbase')),
126126
EXCLUDE.append(pjoin('IPython', 'testing', 'parametric'))
127127
EXCLUDE.append(pjoin('IPython', 'testing', 'util'))
128-
EXCLUDE.append(pjoin('IPython', 'testing', 'tests',
129-
'test_decorators_trial'))
130128

131129
# This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
132130
if sys.platform == 'win32':

IPython/testing/ipunittest.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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 <[email protected]>
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()

IPython/testing/parametric.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Parametric testing on top of twisted.trial.unittest.
22
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).
36
"""
47

58
__all__ = ['parametric','Parametric']

0 commit comments

Comments
 (0)