You are not logged in Log in Join
You are here: Home » Members » edloper » Doctest 1.36 Implementation Summary

Log in
Name

Password

 
 

Doctest 1.36 Implementation Summary

          ==================================================
          doctest.py: Implementation Summary (revision 1.36)
          ==================================================

This section gives a summary of the current design.  I wrote it while
reading through the code, to make sure I understood what all the
pieces did (and to give me a reference to check back with); but it
might be a useful guide to anyone who's trying to familiarize
themselves more with the current code.

Entry points
============
Doctest cases are run by either `run_doctest_examples()` or
`Tester.runstring()`.  (There are other entry points, such as
`Tester.rundoc()`, but they all call into either `run_doctest_examples()`
or `Tester.runstring()`; see below for more info on other entry points.)

Each of these routines calls `_extract_examples()` to pull examples
out of the docstring, and then calls `_run_examples()` to run them.
They both return a tuple `(num_failures, num_doctest_cases)` (which
they get as the return value from `_run_examples()`).
`Tester.runstring()` also keeps a running total of `num_failures` and
`num_doctest_cases` in instance variables.

Since `run_doctest_examples()` is pulls the docstring out of an
object, it needs to do a little more work.  In particular, it checks
for errors & casts to str while pulling out the docstring (is this
unicode-unfriendly?); and it gets compiler flags by checking
__future__ flags from globals.

_extract_examples
=================
`_extract_examples(s)` takes a string containing a doctest case, and
returns a list of those examples.  Each doctest case is encoded as a
tripple `(source, outcome, lineno)`, where:

  - `source` is the source code string.  It ends with a newline iff 
    `source` spans multiple lines.
  - `outcome` is the expected output, if any, or an empty string if no
    output is expected.  `outcome` ends in a newline iff output is
    expected
  - `lineno` is the line number of the first line of the doctest case
    within the input string.  This line number is zero-based.

`_extract_examples()` is basically implemented as an ad-hoc parser that
loops through the lines of the docstring.

_run_examples
=============
`_run_examples()` is basically a wrapper for
`_run_examples_inner()`.  Here's what it does:

  - Redirect stdout to a dummy object (`_SpoofOut`) that captures
    output.
  - Make a shallow copy of the gloals.
  - Call `_run_examples_inner()` to do the real work.  

After `_run_examples_inner()` returns, `_run_examples()` restores
`sys.stdout`; clears the copy of globals (for gc reasons); and returns
what `_run_examples_inner()` returned (i.e., a tuple `(num_failures,
num_doctest_cases)`).

_run_examples_inner
===================
This is where the real work happens.  It takes a bunch of parameters:

  - `out`: A function for writing output.  Currently, this is set
    to `sys.stdout.write` by `_run_examples()`.
  - `fakeout`: The dummy output object (`_SpoofOut`), used to capture
    the output of each doctest case. 
  - `examples`: A list of `(source, outcome, lineno)` tripples, as
    returned by `_extract_examples()`.
  - `globs`: The set of globals used as an environment to evaluate
    doctest cases.  
  - `verbose`: If true, then print a message to `out` before running
    each example, indicating what example is being run and what the
    expected output is.
  - `name`: The "name" of the collection of examples (typically the name
    of the object that `_extract_examples()` was run on).  This is used
    in the output to `out`.
  - `compileflags`: A set of compiler flags for the builtin `compile()`
    function.  This is used to correctly handle `__future__` imports.
  - `optionflags`: A set of flags for options to doctest itself.  
    Currently, only one flag is supported: `DONT_ACCEPT_TRUE_FOR_1`.

`_run_examples_inner()` loops through each doctest example, compiles
it, and runs it (with output directed to `fakeout`).  It then extracts
the output, and comparse it to the expected output.  If they differ,
it calls `out` with an error message.

Exceptions are handled separately: if an exception is raised, then the
expected output is compared against a traceback template; if they
match, and the exception type/message match, then the test passes;
otherwise, it fails.

All examples are evaluated in the same globals environment, so
variables can be shared between the doctest cases in `examples` (e.g.,
one doctest case can create a variable, and a later doctest case can
use it).

Once all examples have been run, `_run_examples_inner()` returns a
tuple `(num_failures, num_doctest_cases)`.

Other Entry Points
==================
The `Tester` class defines a number of entry points: `Tester.rundoc()`,
`Tester.rundict()`, and `Tester.run__test__()`.  Also, the function
`testmod` creates a `Tester` class and uses it to test every object
in a module.  Finally, there are some entry points to make it easier
to interface with the `unittest` module.

Tester.rundoc()
---------------
  `Tester.rundoc(obj)` calls `run_doctest_examples` to extract and
  process the doctest cases in the given object's docstring.  
  
  If `obj` is a class, then `Tester.rundoc` puts together a dictionary
  mapping each attributes name to an object with the right docstrings.
  (It needs to do some acrobatics to get the right objects when wrappers
  such as `staticmethod` and `classmethod` are used.)  It then calls
  `Tester.run__test__()` on the dictionary, and merges the return value
  into the return value from `run_doctest_examples` (both return a
  `(num_failures, num_doctest_cases)` tuple).
  
  Note that running `Tester.rundoc()` on a class will recurse into
  contained objects; but calling it on a module will *not* recurse into
  contained objects.
  
Tester.rundict() and Tester.run__test__()
-----------------------------------------
  Both of these functions basically take a dictionary from names to
  objects, and calls `rundoc` and/or `runstring` on those objects.
  There are a number of annoying and useless differences between the two
  (eg one uses the variable `thisname` for the dictionary key; the other
  uses `thisname` for the dictionary key *plus* a given prefix).  But
  there are also a couple of real differences:
  
    - `rundict` uses `isprivate` to ignore anything with a "private"
      name; but `run__test__` looks at every dictionary item.
  
    - `rundict` excludes any object that is not defined in a given
      module.  (This is to avoid running doctests on imported objects.)
  
    - `rundict` ignores everything but functions and classes; but
      `run__test__` also processes strings.  Also, if you give 
      `run__test__` anything *other* than a function, class, or
      string, then it raises an exception.
  
  I don't see any reason why these two have to duplicate code (and do
  things differently); I think they could be folded into a single
  function pretty easily, which would reduce the number of places for
  bugs to hide.

testmod
-------
  `testmod()` is one of the more common entry points for doctest.  It
  basically just creates a Tester object and uses it to run the tests
  in every docstring in a given module.
  
  It takes the following arguments:
  
    - `m`: The module to test (use sys.modules['__main__'] if not
      specified).
    - `name`: The name of the module (use `m.__name__` if not
      specified).
    - `globs`: The set of globals to use (use `m.__dict__` if not 
      specified).
    - `verbose`: Verbosity level for the `Tester` object.
    - `isprivate`: Private-detection function for the `Tester` object.
    - `report`: If true, then print a report at the end.
    - `optionflags`: flags for the `Tester` object (passed through to
      `_run_examples_inner`).
  
  It calls `Tester.rundict` on the module's `__dict__`; and if the
  module defines a `__test__` attribute, then it `Tester.run__test__`
  on it.
  
  It also creates a global "master" trainer, which it uses to keep
  track of the total number of errors that have been generated across
  multiple calls to `testmod()`.
  
  Like most of the other entry points, it returns a tuple
  `(num_failures, num_doctest_cases)`.

Utility Functions and Classes
=============================
_SpoofOut
---------
  :Used by: `_run_examples()` and `_run_examples_inner()` (to replace
            `sys.stdout` and as a `writer` for `traceback.print_exc`).
  
  This is a fairly streight-forward object that acts like a file open
  for writing, and captures any output into a string.  It's used to
  capture output when running doctest cases, so it can be compared
  against the expected output.  If the captured output doesn't already
  end with a newline, then `_SpoofOut.get()` adds one.
  
_tag_out
--------
  :Used by: `_run_examples_inner()`.
  
  A simple utility function to print one or more `(tag, message)`
  pairs.  E.g.:
  
      >>> _tag_out(sys.stdout.write, ('tag', 'message'))
      tag: message
      >>> _tag_out(sys.stdout.write, ('tag', 'multiline\nmessage\n'))
      tag:
      multiline
      message
  
  It distinguishes single-line messages from multiline messages by
  checking whether the last charater is a newline.
  
_extract_future_flags
---------------------
  :Used by: `run_docstring_examples()` and `Tester.__init__()`
  
  This function checks what `__future__` features are used in a single
  set of globals, and returns a corresponding set of compiler flags
  (suitable for use with the `compile()` builtin).
  
_is_private
-----------
  :Used by: `Tester.__init__()`, as a default value for the `isprivate`
            parameter to `Tester.__init__` (`isprivate` is used by
            `Tester.rundict()`).
  
  A very simple function that returns true iff a given name is private,
  according to python's naming conventions.
  
_from_module
------------
  :Used by: `Tester.rundict()`
  
  Given a function or class, return true iff it was defined in a given
  module.
  
Tester
======
The tester class is a basic test manager, that provides a wrapper for
running tests, and keeps track of overall statistics.  Most of its
methods function as "entry points" for running tests, and were
discussed above.  It also defines a few other methods:

  - `Tester.summarize()` prints a summary of the results of the tests
    that have been run by the tester object.  It's based off of
    the `name2ft` instance variable, which maps names to tuples of
    `(num_failed, num_doctest_cases)`.
  - `Tester.__record_outcome()` is called by `Tester.rundoc()` and 
     `Tester.runstring()`, and is responsible for updating `name2ft`
  - `Tester.merge()` updates `name2ft` by merging the items from
    another `Tester` object's `name2ft` dictionary.
  - `Tester.__runone()` is called by `Tester.rundict()`, and is 
    responsible for filtering out private names.  For public names,
    it just calls `Tester.rundoc()`.

DocTestSuite (unittest support)
===============================
`DocTestSuite` returns a unittest test suite object that runs all the
doctest cases in a given module.  The given module can be a module
object, a string, or `None` (meaning that the current module should be
tested).

[I haven't had a chance to go through this in detail yet]

Debugger
========
The debugger will run a specified object's docstring, and then enter
the debugger (with `pdb.post_mortem()`).

[I haven't had a chance to go through this in detail yet]