ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: A simple and useful doctester for your documentation
Submitter: Michele Simionato (other recipes)
Last Updated: 2005/04/16
Version no: 1.2
Category: Programs

 

5 stars 1 vote(s)


Description:

The doctester extracts code from stdin and tests it using the doctest module in
the standard library. It can be invoked from the command line, but it is
best called from you editor of choice. I just give an example for Emacs.

Source: Text Source

#!/usr/bin/env python
# Author: michele.simionato@gmail.com
"""\
Filter passing stdin through doctest. Example of usage:
$ doctester.py -v < file.txt
"""
import sys, doctest, textwrap, re, types

# regular expressions to identify code blocks of the form
#<scriptname.py> ... </scriptname.py>
DOTNAME = r'\b[a-zA-Z_][\w\.]*', # identifier with or without dots
SCRIPT = re.compile(r'(?s)#<(%s)>(.*?)#</\1>' % DOTNAME)

# a simple utility to extract the scripts contained in the original text
def scripts(txt):
    for MO in SCRIPT.finditer(txt):
        yield MO.group(1), textwrap.dedent(MO.group(2))

# save the scripts in the current directory
def savescripts(txt):
    scriptdict = {}
    for scriptname, script in scripts(txt): # read scripts
        if scriptname not in scriptdict:
            scriptdict[scriptname] = script
        else:
            scriptdict[scriptname] += script
    for scriptname in scriptdict: # save scripts
        code = '# ' + scriptname + scriptdict[scriptname]
        print >> file(scriptname, 'w'), code

# based on a clever trick: it converts the original text into the docstring of
# a dynamically generated module; works both for Python 2.3 and 2.4
def runtests(txt, verbose=False):
    savescripts(txt)
    dynmod = types.ModuleType('<current-buffer>', txt)   
    failed, tot = doctest.testmod(dynmod, verbose=verbose)
    if not verbose:
        print >> sys.stderr, "doctest: %s failed of %s" % (failed, tot)
    return failed, tot

if __name__=='__main__':
    try: set # need sets for option parsing
    except NameError: import sets; set = sets.Set # for Python 2.3
    valid_options = set("-v -h".split())
    options = set(sys.argv[1:])
    assert options < valid_options, "Unrecognized option"
    if "-h" in options: # print usage message and exit
        sys.exit(__doc__)
    runtests(sys.stdin.read(), "-v" in options)

Discussion:

I use this little script a lot: to test my posts to
c.l.py, to test my articles, and to test my libraries.
Since the script is extremely simple and useful, I thought I
will share it.

The first thing you need is a text like this, with
cut and pasted interpreter sessions:

>>> 1 + 1
2

The doctester will look for snippets of this form and will
test them. The magic is performed by the doctest module in the
standard library. I have just added the possibity of inserting
named modules in the text file, like this one::

#\<example_module.py\>

a = 1

#\</example_module.py\>

The doctester will extract code like this and save it in your
current directory, under the name ``example_module.py``,
*before* running the tests. In this way you can import
the module in your tests:

>>> from example_module import a
>>> print a
1

You may define any number of modules in the same way.
You can also add code to a previously defined module, simply by
repeating the module name::

#\<example_module.py\>

b = 2

#\</example_module.py\>


>>> from example_module import b
>>> print b
2

Ideally, in future extensions, it will be possible to insert snippets
of code in other languages (for instance bash).

The doctester can be used from the command line or called from
an external program. For instance, you could pass to the doctester
this text you are reading now: suppose it is stored in a file
called doctester.txt, you will get

::

$ python doctester.py
doctest: 0 failed of 5

or, if you prefer a more explicit output,

::

$ python doctester.py -v < doctester.txt
Trying:
1 + 1
Expecting:
2
ok
Trying:
from example_module import a
Expecting nothing
ok
Trying:
print a
Expecting:
1
ok
Trying:
from example_module import b
Expecting nothing
ok
Trying:
print b
Expecting:
2
ok
1 items passed all tests:
5 tests in <current-buffer>
5 tests in 1 items.
5 passed and 0 failed.
Test passed.

The message says that the tests were defined in '<current-buffer>': the
reason is that I usually call the doctester from Emacs, when I am editing
the text. If you have Python 2.4 and the doctester in installed in
your current Python path, you can just put the following in your .emacs::

;; passing the current buffer to an external tool
(defun run-doctest ()
(interactive)
(shell-command-on-region (beginning-of-buffer)
(end-of-buffer)
"python2.4 -m doctester"
current-prefix-arg
current-prefix-arg))

(defun run-doctest-verbose ()
(interactive)
(shell-command-on-region (beginning-of-buffer)
(end-of-buffer)
"python2.4 -m doctester -v"
current-prefix-arg
current-prefix-arg))

;; F6 for regular output, SHIFT-F6 for verbose output
(global-set-key [f6] 'run-doctest)
(global-set-key [(shift f6)] 'run-doctest-verbose)


If you have Python 2.3 you may have to work a bit more, or
may just insert the full pathname of the doctester script.
Obviously you may change the keybindings to whatever you like.
I am pretty sure you can invoke the doctester from the Other Editor
(TM) too ;)

P.S. I have tried everything to get angular brackets displayed
properly in the site, but &lt; and &gt; do not work; so I put
a backslash but you should *not* use it. Damn cookbook site!



Add comment

No comments.



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. IPy Notify

4. Changing return value ...

5. Quantum Superposition

6. Pickle objects under ...

7. Generalized delegates ...

8. Reorder a sequence (uses ...

9. Setting Win32 System ...

10. ObjectMerger




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.