From da07a079781c0392696a0ec05da9cfd2a72068b8 2008-08-02 09:07:37 From: Fernando Perez Date: 2008-08-02 09:07:37 Subject: [PATCH] Complete first pass on testing system. All tests pass on my box. Whew. We now have a first cut of a functioning testing system. Many examples had to be disabled (with a proper decorator), but now we can begin building a robust test suite for all of IPython. The big remaining todo is to fix the handling of the ipython/test namespace synchronization, which is still not 100% correct. Once that is done, some of the currently skipped tests will be reactivated. --- diff --git a/IPython/Magic.py b/IPython/Magic.py index 74cb056..f416884 100644 --- a/IPython/Magic.py +++ b/IPython/Magic.py @@ -61,6 +61,8 @@ from IPython import platutils import IPython.generics import IPython.ipapi from IPython.ipapi import UsageError +from IPython.testing import decorators as testdec + #*************************************************************************** # Utility functions def on_off(tag): @@ -522,7 +524,7 @@ Currently the magic system has the following functions:\n""" rc.automagic = not rc.automagic print '\n' + Magic.auto_status[rc.automagic] - + @testdec.skip_doctest def magic_autocall(self, parameter_s = ''): """Make functions callable without having to type parentheses. @@ -551,8 +553,9 @@ Currently the magic system has the following functions:\n""" 2 -> Active always. Even if no arguments are present, the callable object is called: - In [4]: callable - ------> callable() + In [2]: float + ------> float() + Out[2]: 0.0 Note that even with autocall off, you can still use '/' at the start of a line to treat the first argument on the command line as a function @@ -561,6 +564,8 @@ Currently the magic system has the following functions:\n""" In [8]: /str 43 ------> str(43) Out[8]: '43' + + # all-random (note for auto-testing) """ rc = self.shell.rc @@ -1243,12 +1248,13 @@ Currently the magic system has the following functions:\n""" self.shell.debugger(force=True) + @testdec.skip_doctest def magic_prun(self, parameter_s ='',user_mode=1, opts=None,arg_lst=None,prog_ns=None): """Run a statement through the python code profiler. - Usage:\\ + Usage: %prun [options] statement The given statement (which doesn't require quote marks) is run via the @@ -1293,16 +1299,16 @@ Currently the magic system has the following functions:\n""" abbreviation is unambiguous. The following are the keys currently defined: - Valid Arg Meaning\\ - "calls" call count\\ - "cumulative" cumulative time\\ - "file" file name\\ - "module" file name\\ - "pcalls" primitive call count\\ - "line" line number\\ - "name" function name\\ - "nfl" name/file/line\\ - "stdname" standard name\\ + Valid Arg Meaning + "calls" call count + "cumulative" cumulative time + "file" file name + "module" file name + "pcalls" primitive call count + "line" line number + "name" function name + "nfl" name/file/line + "stdname" standard name "time" internal time Note that all sorts on statistics are in descending order (placing @@ -1328,8 +1334,10 @@ Currently the magic system has the following functions:\n""" '%run -p [prof_opts] filename.py [args to program]' where prof_opts contains profiler specific options as described here. - You can read the complete documentation for the profile module with:\\ - In []: import profile; profile.help() """ + You can read the complete documentation for the profile module with:: + + In [1]: import profile; profile.help() + """ opts_def = Struct(D=[''],l=[],s=['time'],T=['']) # protect user quote marks @@ -1413,6 +1421,7 @@ Currently the magic system has the following functions:\n""" else: return None + @testdec.skip_doctest def magic_run(self, parameter_s ='',runner=None): """Run the named file inside IPython as a program. @@ -1709,6 +1718,7 @@ Currently the magic system has the following functions:\n""" self.shell.safe_execfile(f,self.shell.user_ns, self.shell.user_ns,islog=1) + @testdec.skip_doctest def magic_timeit(self, parameter_s =''): """Time execution of a Python statement or expression @@ -1736,7 +1746,8 @@ Currently the magic system has the following functions:\n""" Default: 3 - Examples:\\ + Examples: + In [1]: %timeit pass 10000000 loops, best of 3: 53.3 ns per loop @@ -1820,7 +1831,8 @@ Currently the magic system has the following functions:\n""" units[order]) if tc > tc_min: print "Compiler time: %.2f s" % tc - + + @testdec.skip_doctest def magic_time(self,parameter_s = ''): """Time execution of a Python statement or expression. @@ -1912,6 +1924,7 @@ Currently the magic system has the following functions:\n""" print "Compiler : %.2f s" % tc return out + @testdec.skip_doctest def magic_macro(self,parameter_s = ''): """Define a set of input lines as a macro for future re-execution. @@ -1941,17 +1954,17 @@ Currently the magic system has the following functions:\n""" For example, if your history contains (%hist prints it): - 44: x=1\\ - 45: y=3\\ - 46: z=x+y\\ - 47: print x\\ - 48: a=5\\ - 49: print 'x',x,'y',y\\ + 44: x=1 + 45: y=3 + 46: z=x+y + 47: print x + 48: a=5 + 49: print 'x',x,'y',y you can create a macro with lines 44 through 47 (included) and line 49 called my_macro with: - In []: %macro my_macro 44-47 49 + In [55]: %macro my_macro 44-47 49 Now, typing `my_macro` (without quotes) will re-execute all this code in one pass. @@ -1972,7 +1985,7 @@ Currently the magic system has the following functions:\n""" can obtain similar results by explicitly executing slices from your input history with: - In []: exec In[44:48]+In[49]""" + In [60]: exec In[44:48]+In[49]""" opts,args = self.parse_options(parameter_s,'r',mode='list') if not args: @@ -2043,6 +2056,7 @@ Currently the magic system has the following functions:\n""" """Alias to %edit.""" return self.magic_edit(parameter_s) + @testdec.skip_doctest def magic_edit(self,parameter_s='',last_call=['','']): """Bring up an editor and execute the resulting code. @@ -2136,47 +2150,47 @@ Currently the magic system has the following functions:\n""" This is an example of creating a simple function inside the editor and then modifying it. First, start up the editor: - In []: ed\\ - Editing... done. Executing edited code...\\ - Out[]: 'def foo():\\n print "foo() was defined in an editing session"\\n' + In [1]: ed + Editing... done. Executing edited code... + Out[1]: 'def foo():n print "foo() was defined in an editing session"n' We can then call the function foo(): - In []: foo()\\ + In [2]: foo() foo() was defined in an editing session Now we edit foo. IPython automatically loads the editor with the (temporary) file where foo() was previously defined: - In []: ed foo\\ + In [3]: ed foo Editing... done. Executing edited code... And if we call foo() again we get the modified version: - In []: foo()\\ + In [4]: foo() foo() has now been changed! Here is an example of how to edit a code snippet successive times. First we call the editor: - In []: ed\\ - Editing... done. Executing edited code...\\ - hello\\ - Out[]: "print 'hello'\\n" + In [5]: ed + Editing... done. Executing edited code... + hello + Out[5]: "print 'hello'n" Now we call it again with the previous output (stored in _): - In []: ed _\\ - Editing... done. Executing edited code...\\ - hello world\\ - Out[]: "print 'hello world'\\n" + In [6]: ed _ + Editing... done. Executing edited code... + hello world + Out[6]: "print 'hello world'n" Now we call it with the output #8 (stored in _8, also as Out[8]): - In []: ed _8\\ - Editing... done. Executing edited code...\\ - hello again\\ - Out[]: "print 'hello again'\\n" + In [7]: ed _8 + Editing... done. Executing edited code... + hello again + Out[7]: "print 'hello again'n" Changing the default editor hook: @@ -2473,7 +2487,8 @@ Defaulting color scheme to 'NoColor'""" #...................................................................... # Functions to implement unix shell-type things - + + @testdec.skip_doctest def magic_alias(self, parameter_s = ''): """Define an alias for a system command. @@ -2489,18 +2504,18 @@ Defaulting color scheme to 'NoColor'""" You can use the %l specifier in an alias definition to represent the whole line when the alias is called. For example: - In [2]: alias all echo "Input in brackets: <%l>"\\ - In [3]: all hello world\\ + In [2]: alias all echo "Input in brackets: <%l>" + In [3]: all hello world Input in brackets: You can also define aliases with parameters using %s specifiers (one per parameter): - In [1]: alias parts echo first %s second %s\\ - In [2]: %parts A B\\ - first A second B\\ - In [3]: %parts A\\ - Incorrect number of arguments: 2 expected.\\ + In [1]: alias parts echo first %s second %s + In [2]: %parts A B + first A second B + In [3]: %parts A + Incorrect number of arguments: 2 expected. parts is an alias to: 'echo first %s second %s' Note that %l and %s are mutually exclusive. You can only use one or @@ -2513,11 +2528,11 @@ Defaulting color scheme to 'NoColor'""" IPython for variable expansion. If you want to access a true shell variable, an extra $ is necessary to prevent its expansion by IPython: - In [6]: alias show echo\\ - In [7]: PATH='A Python string'\\ - In [8]: show $PATH\\ - A Python string\\ - In [9]: show $$PATH\\ + In [6]: alias show echo + In [7]: PATH='A Python string' + In [8]: show $PATH + A Python string + In [9]: show $$PATH /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... You can use the alias facility to acess all of $PATH. See the %rehash @@ -2832,7 +2847,7 @@ Defaulting color scheme to 'NoColor'""" header = 'Directory history (kept in _dh)', start=ini,stop=fin) - + @testdec.skip_doctest def magic_sc(self, parameter_s=''): """Shell capture - execute a shell command and capture its output. @@ -2876,29 +2891,31 @@ Defaulting color scheme to 'NoColor'""" For example: + # all-random + # Capture into variable a - In []: sc a=ls *py + In [1]: sc a=ls *py # a is a string with embedded newlines - In []: a - Out[]: 'setup.py\nwin32_manual_post_install.py' + In [2]: a + Out[2]: 'setup.py\\nwin32_manual_post_install.py' # which can be seen as a list: - In []: a.l - Out[]: ['setup.py', 'win32_manual_post_install.py'] + In [3]: a.l + Out[3]: ['setup.py', 'win32_manual_post_install.py'] # or as a whitespace-separated string: - In []: a.s - Out[]: 'setup.py win32_manual_post_install.py' + In [4]: a.s + Out[4]: 'setup.py win32_manual_post_install.py' # a.s is useful to pass as a single command line: - In []: !wc -l $a.s + In [5]: !wc -l $a.s 146 setup.py 130 win32_manual_post_install.py 276 total # while the list form is useful to loop over: - In []: for f in a.l: + In [6]: for f in a.l: ...: !wc -l $f ...: 146 setup.py @@ -2908,13 +2925,13 @@ Defaulting color scheme to 'NoColor'""" the sense that you can equally invoke the .s attribute on them to automatically get a whitespace-separated string from their contents: - In []: sc -l b=ls *py + In [7]: sc -l b=ls *py - In []: b - Out[]: ['setup.py', 'win32_manual_post_install.py'] + In [8]: b + Out[8]: ['setup.py', 'win32_manual_post_install.py'] - In []: b.s - Out[]: 'setup.py win32_manual_post_install.py' + In [9]: b.s + Out[9]: 'setup.py win32_manual_post_install.py' In summary, both the lists and strings used for ouptut capture have the following special attributes: diff --git a/IPython/iplib.py b/IPython/iplib.py index 8bdb61e..67f0974 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -1010,7 +1010,7 @@ class InteractiveShell(object,Magic): hello In [10]: _ip.IP.complete('x.l') - Out[10]: ['x.ljust', 'x.lower', 'x.lstrip'] + Out[10]: ['x.ljust', 'x.lower', 'x.lstrip'] # random """ complete = self.Completer.complete diff --git a/IPython/ipstruct.py b/IPython/ipstruct.py index 73dbb72..b85e9d5 100644 --- a/IPython/ipstruct.py +++ b/IPython/ipstruct.py @@ -88,8 +88,8 @@ class Struct: initialization): keys can't be numbers. But numeric keys can be used and accessed using the dictionary syntax. Again, an example: - This doesn't work: - py> s=Struct(4='hi') #doctest: +IGNORE_EXCEPTION_DETAIL + This doesn't work (prompt changed to avoid confusing the test system): + ->> s=Struct(4='hi') Traceback (most recent call last): ... SyntaxError: keyword can't be an expression diff --git a/IPython/kernel/tests/test_contexts.py b/IPython/kernel/tests/test_contexts.py index 61bc195..22590d0 100644 --- a/IPython/kernel/tests/test_contexts.py +++ b/IPython/kernel/tests/test_contexts.py @@ -1,3 +1,5 @@ +from __future__ import with_statement + #def test_simple(): if 0: diff --git a/IPython/testing/plugin/decorator_msim.py b/IPython/testing/decorator_msim.py similarity index 100% rename from IPython/testing/plugin/decorator_msim.py rename to IPython/testing/decorator_msim.py diff --git a/IPython/testing/plugin/decorators.py b/IPython/testing/decorators.py similarity index 91% rename from IPython/testing/plugin/decorators.py rename to IPython/testing/decorators.py index 86266c7..c9f8832 100644 --- a/IPython/testing/plugin/decorators.py +++ b/IPython/testing/decorators.py @@ -19,7 +19,7 @@ import inspect # Third-party imports # This is Michele Simionato's decorator module, also kept verbatim. -from decorator_msim import decorator +from decorator_msim import decorator, update_wrapper # Grab the numpy-specific decorators which we keep in a file that we # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy @@ -113,23 +113,12 @@ def make_label_dec(label,ds=None): #----------------------------------------------------------------------------- # Decorators for public use -def skip_doctest(func): - """Decorator - mark a function for skipping its doctest. +skip_doctest = make_label_dec('skip_doctest', + """Decorator - mark a function or method for skipping its doctest. This decorator allows you to mark a function whose docstring you wish to omit from testing, while preserving the docstring for introspection, help, - etc.""" - - # We just return the function unmodified, but the wrapping has the effect - # of making the doctest plugin skip the doctest. - def wrapper(*a,**k): - return func(*a,**k) - - # Here we use plain 'decorator' and not apply_wrapper, because we don't - # need all the nose-protection machinery (functions containing doctests - # can't be full-blown nose tests, so we don't need to prserve - # setup/teardown). - return decorator(wrapper,func) + etc.""") def skip(func): diff --git a/IPython/testing/plugin/decorators_numpy.py b/IPython/testing/decorators_numpy.py similarity index 100% rename from IPython/testing/plugin/decorators_numpy.py rename to IPython/testing/decorators_numpy.py diff --git a/IPython/testing/plugin/Makefile b/IPython/testing/plugin/Makefile index 106933d..d895af8 100644 --- a/IPython/testing/plugin/Makefile +++ b/IPython/testing/plugin/Makefile @@ -7,7 +7,7 @@ NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt #--with-color -SRC=ipdoctest.py setup.py decorators.py +SRC=ipdoctest.py setup.py ../decorators.py plugin: IPython_doctest_plugin.egg-info @@ -32,7 +32,13 @@ iptest: plugin $(NOSE) IPython deco: - $(NOSE0) decorators.py + $(NOSE0) IPython.testing.decorators + +mtest: plugin + $(NOSE) -x IPython.Magic + +ipipe: plugin + $(NOSE) -x IPython.Extensions.ipipe sr: rtest std diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index e41663c..40834e0 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -158,6 +158,16 @@ def is_extension_module(filename): return os.path.splitext(filename)[1].lower() in ('.so','.pyd') +class nodoc(object): + def __init__(self,obj): + self.obj = obj + + def __getattribute__(self,key): + if key == '__doc__': + return None + else: + return getattr(object.__getattribute__(self,'obj'),key) + # Modified version of the one in the stdlib, that fixes a python bug (doctests # not found in extension modules, http://bugs.python.org/issue3158) class DocTestFinder(doctest.DocTestFinder): @@ -196,6 +206,10 @@ class DocTestFinder(doctest.DocTestFinder): add them to `tests`. """ + if hasattr(obj,"skip_doctest"): + #print 'SKIPPING DOCTEST FOR:',obj # dbg + obj = nodoc(obj) + doctest.DocTestFinder._find(self,tests, obj, name, module, source_lines, globs, seen) @@ -591,6 +605,12 @@ class IPDocTestRunner(doctest.DocTestRunner,object): # for all examples, most of which won't be calling %run anyway). _run_ns_sync.test_globs = test.globs + # dbg + ## print >> sys.stderr, "Test:",test + ## for ex in test.examples: + ## print >> sys.stderr, ex.source + ## print >> sys.stderr, 'Want:\n',ex.want,'\n--' + return super(IPDocTestRunner,self).run(test, compileflags,out,clear_globs) diff --git a/IPython/testing/plugin/test_refs.py b/IPython/testing/plugin/test_refs.py index c83551b..2c6e88b 100644 --- a/IPython/testing/plugin/test_refs.py +++ b/IPython/testing/plugin/test_refs.py @@ -5,7 +5,7 @@ import inspect # Third party # Our own -import decorators as dec +from IPython.testing import decorators as dec #----------------------------------------------------------------------------- # Utilities @@ -55,7 +55,65 @@ def doctest_bad(x,y=1,**k): >>> 1+1 3 """ - z=2 + print 'x:',x + print 'y:',y + print 'k:',k + + +def call_doctest_bad(): + """Check that we can still call the decorated functions. + + >>> doctest_bad(3,y=4) + x: 3 + y: 4 + k: {} + """ + pass + + +# Doctest skipping should work for class methods too +class foo(object): + """Foo + + Example: + + >>> 1+1 + 2 + """ + + @dec.skip_doctest + def __init__(self,x): + """Make a foo. + + Example: + + >>> f = foo(3) + junk + """ + print 'Making a foo.' + self.x = x + + @dec.skip_doctest + def bar(self,y): + """Example: + + >>> f = foo(3) + >>> f.bar(0) + boom! + >>> 1/0 + bam! + """ + return 1/y + + def baz(self,y): + """Example: + + >>> f = foo(3) + Making a foo. + >>> f.baz(3) + True + """ + return self.x==y def test_skip_dt_decorator(): diff --git a/scripts/ipython b/scripts/ipython index 66b221e..1f9744d 100755 --- a/scripts/ipython +++ b/scripts/ipython @@ -24,4 +24,5 @@ are trapped first by Python itself. """ import IPython.Shell + IPython.Shell.start().mainloop()