[Checkins] SVN: manuel/trunk/ ported to Python 3
Benji York
cvs-admin at zope.org
Mon Apr 16 19:07:47 UTC 2012
Log message for revision 125148:
ported to Python 3
Changed:
U manuel/trunk/CHANGES.txt
U manuel/trunk/buildout.cfg
U manuel/trunk/setup.py
D manuel/trunk/src/index.txt
U manuel/trunk/src/manuel/README.txt
U manuel/trunk/src/manuel/__init__.py
U manuel/trunk/src/manuel/bugs.txt
U manuel/trunk/src/manuel/codeblock.py
U manuel/trunk/src/manuel/doctest.py
A manuel/trunk/src/manuel/index.txt
U manuel/trunk/src/manuel/table-example.txt
U manuel/trunk/src/manuel/testing.py
U manuel/trunk/src/manuel/tests.py
-=-
Modified: manuel/trunk/CHANGES.txt
===================================================================
--- manuel/trunk/CHANGES.txt 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/CHANGES.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,6 +1,12 @@
CHANGES
=======
+1.6.0 (2012-04-16)
+------------------
+
+- Ported to Python 3, still works in 2.6 and up.
+
+
1.5.0 (2011-03-08)
------------------
Modified: manuel/trunk/buildout.cfg
===================================================================
--- manuel/trunk/buildout.cfg 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/buildout.cfg 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,26 +1,16 @@
[buildout]
develop = .
-parts = test interpreter sphinx-docs-html build-docs nosetests releaser
-allow-picked-versions = false
+parts = test interpreter sphinx-docs-html build-docs
+#allow-picked-versions = false
use-dependency-links = false
versions = versions
-[releaser]
-recipe = zc.recipe.egg
-eggs = zest.releaser
-
[test]
recipe = zc.recipe.testrunner
eggs = manuel [tests]
defaults = '--tests-pattern tests --exit-with-status -1 --auto-color'.split()
working-directory = .
-# XXX doesn't work yet, either fix or rip out
-[nosetests]
-recipe = pbp.recipe.noserunner
-eggs = manuel
-working-directory = ${buildout:directory}
-
[interpreter]
recipe = zc.recipe.egg
eggs = manuel
@@ -33,7 +23,7 @@
docutils
Sphinx
scripts = sphinx-build=docs
-base-sphinx-args = ('-W -N -c ${buildout:directory}/sphinx ${buildout:directory}/src ${buildout:directory}/docs'.split())
+base-sphinx-args = ('-N -c ${buildout:directory}/sphinx ${buildout:directory}/src/manuel ${buildout:directory}/docs'.split())
arguments = sys.argv + ${sphinx-docs-html:base-sphinx-args}
initialization =
@@ -45,21 +35,17 @@
cmds = ${buildout:directory}/bin/docs
[versions]
-Jinja2 = 2.1.1
-PILwoTk = 1.1.6.3
-Pygments = 1.0
-Sphinx = 0.6.1
-distribute = 0.6.13
-docutils = 0.5
+Jinja2 = 2.6
+Pygments = 1.5
+distribute = 0.6.26
+docutils = 0.8.1
iw.recipe.cmd = 0.3
-nose = 0.11.1
-pbp.recipe.noserunner = 0.2.6
-setuptools = 0.6c11
-zc.buildout = 1.4.3
-zc.recipe.egg = 1.2.2
-zc.recipe.testrunner = 1.3.0
-zest.releaser = 3.7
-zope.exceptions = 3.6.0
-zope.interface = 3.5.1
-zope.testing = 3.10.0
-zope.testrunner = 4.0.0b2
+setuptools = 0.6c12dev-r88846
+six = 1.1.0
+sphinx = 1.1.3
+z3c.recipe.scripts = 1.0.1
+zc.recipe.testrunner = 1.4.0
+zope.exceptions = 3.7.1
+zope.interface = 3.8.0
+zope.testing = 4.1.1
+zope.testrunner = 4.0.4
Modified: manuel/trunk/setup.py
===================================================================
--- manuel/trunk/setup.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/setup.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -32,7 +32,11 @@
zip_safe=False,
author='Benji York',
author_email='benji at benjiyork.com',
- description= 'Manuel lets you build tested documentation.',
+ description='Manuel lets you build tested documentation.',
+ classifiers=[
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 3',
+ ],
license='ZPL',
extras_require={
'tests': tests_require,
@@ -41,6 +45,7 @@
test_suite = 'manuel.tests.test_suite',
install_requires=[
'setuptools',
+ 'six',
],
include_package_data=True,
long_description = long_description,
Deleted: manuel/trunk/src/index.txt
===================================================================
--- manuel/trunk/src/index.txt 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/index.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,996 +0,0 @@
-======
-Manuel
-======
-
-Manuel lets you mix and match traditional doctests with custom test syntax.
-
-Several plug-ins are included that provide new test syntax (see
-:ref:`functionality`). You can also create your own plug-ins.
-
-For example, if you've ever wanted to include a large chunk of Python in a
-doctest but were irritated by all the ">>>" and "..." prompts required, you'd
-like the :mod:`manuel.codeblock` module. It lets you execute code using
-Sphinx-style ".. code-block:: python" directives. The markup looks like
-this::
-
- .. code-block:: python
-
- import foo
-
- def my_func(bar):
- return foo.baz(bar)
-
-Incidentally, the implementation of :mod:`manuel.codeblock` is only 23 lines of
-code.
-
-The plug-ins included in Manuel make good examples while being quite useful in
-their own right. The Manuel documentation makes extensive use of them as well.
-Follow the "Show Source" link to the left to see the `reST
-<http://docutils.sourceforge.net/rst.html>`_ source of this document.
-
-For a large example of creating test syntax, take a look at the
-:ref:`fit-table-example` or for all the details, :ref:`theory-of-operation`.
-
-To see how to get Manuel wired up see :ref:`getting-started`.
-
-.. contents::
-
-
-.. reset-globs
-.. _functionality:
-
-Included Functionality
-======================
-
-Manuel includes several plug-ins out of the box:
-
-:ref:`manuel.capture <capture>`
- stores regions of a document in variables for later processing
-
-:ref:`manuel.codeblock <code-blocks>`
- executes code in ".. code-block:: python" blocks
-
-:ref:`manuel.doctest <doctest>`
- provides traditional doctest processing as a Manuel plug-in
-
-:ref:`manuel.footnote <footnotes>`
- executes code in reST-style footnodes each time they're referenced (good
- for getting incidental code out of the main flow of a document)
-
-:ref:`manuel.ignore <ignore>`
- ignores parts of a document while running tests
-
-:ref:`manuel.isolation <isolation>`
- makes it easier to have test isolation in doctests
-
-:ref:`manuel.testcase <testcase>`
- identify parts of tests as individual test cases so they can be run
- independently
-
-
-.. reset-globs
-.. _getting-started:
-
-Getting Started
-===============
-
-The plug-ins used for a test are composed together using the "+" operator.
-Let's say you wanted a test that used doctest syntax as well as footnotes. You
-would create a Manuel instance to use like this:
-
-.. code-block:: python
-
- import manuel.doctest
- import manuel.footnote
-
- m = manuel.doctest.Manuel()
- m += manuel.footnote.Manuel()
-
-You would then pass the Manuel instance to a :class:`manuel.testing.TestSuite`,
-including the names of documents you want to process:
-
-.. ignore-next-block
-.. code-block:: python
-
- manuel.testing.TestSuite(m, 'test-one.txt', 'test-two.txt')
-
-
-Using unittest
---------------
-
-The simplest way to get started with Manuel is to use :mod:`unittest` to run
-your tests:
-
-.. code-block:: python
-
- import manuel.codeblock
- import manuel.doctest
- import manuel.testing
- import unittest
-
- def test_suite():
- m = manuel.doctest.Manuel()
- m += manuel.codeblock.Manuel()
- return manuel.testing.TestSuite(m, 'test-one.txt', 'test-two.txt')
-
- if __name__ == '__main__':
- unittest.TextTestRunner().run(test_suite())
-
-
-Using zope.testing
-------------------
-
-If you want to use a more featureful test runner you can use zope.testing's
-test runner (usable stand-alone -- it isn't dependent on the Zope application
-server). Create a file named :file:`tests.py` with a :func:`test_suite`
-function that returns a test suite.
-
-The suite can be either a :class:`manuel.testing.TestSuite` object or a
-:class:`unittest.TestSuite` as demonstrated below.
-
-.. code-block:: python
-
- import manuel.codeblock
- import manuel.doctest
- import manuel.testing
-
- def test_suite():
- suite = unittest.TestSuite()
-
- # here you add your other tests to the suite...
-
- # now you can add the Manuel tests
- m = manuel.doctest.Manuel()
- m += manuel.codeblock.Manuel()
- suite.addTest(manuel.testing.TestSuite(m,
- 'test-one.txt', 'test-two.txt'))
-
- return suite
-
-
-Others
-------
-
-If you know out how to make Manuel work with other test runners (nose, py.test,
-etc.), please `send me an email`_ and I'll expand this section.
-
-.. _send me an email: benji+manuel at benjiyork.com
-
-
-Customizing the TestCase class
-------------------------------
-
-Manuel has its own :class:`manuel.testing.TestClass` class that
-:class:`manuel.testing.TestSuite` uses. If you want to customize it, you
-can pass in your own class to `TestSuite`.
-
-.. code-block:: python
-
- import os.path
- import manuel.testing
-
- class StripDirsTestCase(manuel.testing.TestCase):
- def shortDescription(self):
- return os.path.basename(str(self))
- suite = manuel.testing.TestSuite(
- m, path_to_test, TestCase=StripDirsTestCase)
-
- >>> list(suite)[0].shortDescription()
- 'bugs.txt'
-
-
-.. reset-globs
-.. _doctest:
-
-Doctests
-========
-
-Manuel is all about making testable documents and well-documented tests. Of
-course, Python's doctest module is a long-standing fixture in that space, so it
-only makes sense for Manuel to support doctest syntax.
-
-Handling doctests is easy:
-
-.. ignore-next-block
-.. code-block:: python
-
- import manuel.doctest
-
- m = manuel.doctest.Manuel()
- suite = manuel.testing.TestSuite(m, 'my-doctest.txt')
-
-Of course you can mix in other Manuel syntax plug-ins as well (including ones
-you write yourself).
-
-.. ignore-next-block
-.. code-block:: python
-
- import manuel.doctest
- import manuel.codeblock
-
- m = manuel.doctest.Manuel()
- m += manuel.codeblock.Manuel()
- suite = manuel.testing.TestSuite(m, 'my-doctest-with-code-blocks.txt')
-
-The :class:`manuel.doctest.Manuel` constructor also takes :data:`optionflags`
-and :data:`checker` arguments.
-
-.. ignore-next-block
-.. code-block:: python
-
- m = manuel.doctest.Manuel(optionflags=optionflags, checker=checker)
-
-See the `doctest documentation <http://docs.python.org/library/doctest.html>`_
-for more information about the `available options
-<http://docs.python.org/library/doctest.html#doctest-options>`_ and `output
-checkers <http://docs.python.org/library/doctest.html#outputchecker-objects>`_
-
-
-.. note:: :mod:`zope.testing.renormalizing` provides an :class:`OutputChecker`
- for smoothing out differences between actual and expected output for things
- that are hard to control (like memory addresses and time). See the
- `module's doctests <http://svn.zope.org/zope.testing/trunk/src/zope/testing/renormalizing.py?view=markup>`_
- for more information on how it works.
-
-
-.. reset-globs
-.. _capture:
-
-Capturing Blocks
-================
-
-When writing documentation the need often arises to describe the contents of
-files or other non-Python information. You may also want to put that
-information under test. :mod:`manuel.capture` helps with that.
-
-For example, if you were writing the problems for a programming contest, you
-might want to describe the input and output files for each challenge, but you
-want to be sure that your examples are correct.
-
-To do that you might write your document like this:
-
-::
-
- Challenge 1
- ===========
-
- Write a program that sorts the numbers in a file.
-
-
- Example
- -------
-
- Given this example input file::
-
- 6
- 1
- 8
- 20
- 11
- 65
- 2
-
- .. -> input
-
- Your program should generate this output file::
-
- 1
- 2
- 6
- 8
- 11
- 20
- 65
-
- .. -> output
-
- >>> input_lines = input.splitlines()
- >>> correct = '\n'.join(map(str, sorted(map(int, input_lines)))) + '\n'
- >>> output == correct
- True
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> import manuel.capture
- >>> m = manuel.capture.Manuel()
- >>> import manuel.doctest
- >>> m += manuel.doctest.Manuel()
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
-
-This uses the syntax implemented in :mod:`manuel.capture` to capture a block of
-text into a variable (the one named after "->").
-
-Whenever a line of the structure ".. -> VAR" is detected, the text of the
-*previous* block will be stored in the given variable.
-
-.. the paragraph below could be phrased better
-
-Of course, lines that start with ".. " are reST comments, so when the document
-is rendered with docutils or Sphinx, the tests will dissapear and only the
-intended document contents will remain. Like so::
-
- Challenge 1
- ===========
-
- Write a program that sorts the numbers in a file.
-
-
- Example
- -------
-
- Given this example input file::
-
- 6
- 1
- 8
- 20
- 11
- 65
- 2
-
- Your program should generate this output file::
-
- 1
- 2
- 6
- 8
- 11
- 20
- 65
-
-
-.. reset-globs
-.. _code-blocks:
-
-Code Blocks
-===========
-
-`Sphinx <http://sphinx.pocoo.org/>`_ and other docutils `extensions
-<http://docutils.sourceforge.net/sandbox/code-block-directive/docs/syntax-highlight.html>`_
-provide a `"code-block" directive <http://sphinx.pocoo.org/markup/code.html>`_,
-which allows inlined snippets of code in reST documents.
-
-The :mod:`manuel.codeblock` module provides the ability to execute the contents
-of Python code-blocks. For example::
-
- .. code-block:: python
-
- print 'hello'
-
-.. Let's create a reST document with a code block.
-
- >>> import manuel.codeblock
- >>> document = manuel.Document("""
- ... Here is a code-block:
- ...
- ... .. code-block:: python
- ...
- ... x = 'hello'
- ...
- ... A little prose to separate the examples.
- ...
- ... >>> print x
- ... hello
- ...
- ... """)
-
-.. Since the above document mixes code-blocks and doctests, we'll mix in the
- doctest handler.
-
- >>> import manuel.doctest
- >>> m = manuel.codeblock.Manuel()
- >>> m += manuel.doctest.Manuel()
- >>> document.process_with(m, globs={})
-
- Both code blocks were found (for a total of five regions -- text, block,
- text, block, and text):
-
- >>> len(list(document))
- 5
-
- We can see that none of the tests in the document failed:
-
- >>> print document.formatted(),
-
-If the code-block generates some sort of error...
-
-.. code-block:: python
-
- .. code-block:: python
-
- print does_not_exist
-
-.. -> source
-
- >>> document = manuel.Document(source, location='fake.txt')
-
-.. the document above was specially formulated to have nothing before or after
- the code-block
-
- >>> document.source.startswith('.. code-block')
- True
- >>> document.source.endswith('print does_not_exist\n')
- True
-
-...that error will be reported:
-
- >>> document.process_with(m, globs={})
- Traceback (most recent call last):
- ...
- NameError: name 'does_not_exist' is not defined
-
-If you find that you want to include a code-block in a document but don't want
-Manuel to execute it, use :ref:`manuel.ignore <ignore>` to ignore that
-particular block.
-
-
-Invisible Code Blocks
----------------------
-
-At times you'll want to have a block of code that is executed but not displayed
-in the rendered document (like some setup for later examples).
-
-When using doctest's native format (">>>") that's easy to do, you just put the
-code in a reST comment, like so:
-
-::
-
- .. this is some setup, it is hidden in a reST comment
-
- >>> a = 5
- >>> b = a + 3
-
-However, if you want to include a relatively large chunk of Python, you'd
-rather use a code-block, but that means that it will be included in the
-rendered document. Instead, :mod:`manuel.codeblock` also understands a variant
-of the code-block directive that is actually a reST comment: "..
-invisible-code-block:: python"::
-
- .. invisible-code-block:: python
-
- a = 5
- b = a + 3
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> document.process_with(m, globs={})
- >>> print document.formatted()
-
-.. note:: The "invisible-code-block" directive will work with either one or two
- colons. The reason is that reST processers (like docutils and Sphinx) will
- generate an error for unrecognized directives (like invisible-code-block).
- Therefore you can use a single colon and the line will be interpreted as a
- comment instead.
-
-.. the single-colon variant works too
-
- >>> document = manuel.Document("""
- ...
- ... .. invisible-code-block: python
- ...
- ... raise RuntimeError('it worked!')
- ...
- ... """)
- >>> document.process_with(m, globs={})
- Traceback (most recent call last):
- ...
- RuntimeError: it worked!
-
-
-.. reset-globs
-.. _footnotes:
-
-Footnotes
-=========
-
-The :mod:`manuel.footnote` module provides an implementation of reST footnote
-handling, but instead of just plain text, the footnotes can contain any syntax
-Manuel can interpret including doctests.
-
- >>> import manuel.footnote
- >>> m = manuel.footnote.Manuel()
-
-Here's an example of combining footnotes with doctests:
-
-.. so we also need the doctest Manuel plug-in
-
- >>> import manuel.doctest
- >>> m += manuel.doctest.Manuel()
-
-::
-
- Here we reference a footnote. [1]_
-
- >>> x
- 42
-
- Here we reference another. [2]_
-
- >>> x
- 100
-
- .. [1] This is a test footnote definition.
-
- >>> x = 42
-
- .. [2] This is another test footnote definition.
-
- >>> x = 100
-
- .. [3] This is a footnote that will never be executed.
-
- >>> raise RuntimeError('nooooo!')
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
-
-.. The order of examples in footnotes is preserved. If not, the document below
- would generate an error because "a" won't be defined when "b = a + 1" is
- evaluated.
-
- >>> document = manuel.Document("""
- ... Here we want some imports to be done. [foo]_
- ...
- ... >>> a + b
- ... 3
- ...
- ... A little prose to separate the examples.
- ...
- ... .. [foo] Do something
- ...
- ... >>> a = 1
- ...
- ... >>> b = a + 1
- ...
- ... """)
- >>> document.process_with(m, globs={})
- >>> print document.formatted()
-
-It is also possible to reference more than one footnote on a single line.
-
-::
-
- This line has several footnotes on it. [1]_ [2]_ [3]_
-
- >>> z
- 105
-
- A little prose to separate the examples.
-
- .. [1] Do something
-
- >>> w = 3
-
- .. [2] Do something
-
- >>> x = 5
-
- .. [3] Do something
-
- >>> y = 7
-
- >>> z = w * x * y
-
-.. -> source2
-
- >>> document = manuel.Document(source)
- >>> document.process_with(m, globs={})
- >>> print document.formatted()
-
-
-.. reset-globs
-.. _ignore:
-
-Ignoring Blocks
-===============
-
-.. reset-globs
-
-Occasionally the need arises to ignore a block of markup that would otherwise
-be parsed by a Manuel plug-in.
-
-For example, this document has a code-block that will generate a syntax error::
-
- The following is invalid Python.
-
- .. code-block:: python
-
- def foo:
- pass
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> import manuel.codeblock
- >>> m = manuel.codeblock.Manuel()
-
-We can see that when executed, the SyntaxError escapes.
-
- >>> import manuel.codeblock
- >>> m = manuel.codeblock.Manuel()
- >>> document.process_with(m, globs={})
- Traceback (most recent call last):
- ...
- File "<memory>:4", line 2
- def foo:
- ^
- SyntaxError: invalid syntax
-
-The :mod:`manuel.ignore` module provides a way to ignore parts of a document
-using a directive ".. ignore-next-block".
-
-Because Manuel plug-ins are executed in the order they are accumulated, we want
-:mod:`manuel.ignore` to be the base Manuel object, with any additional plug-ins
-added to it.
-
-.. code-block:: python
-
- import manuel.ignore
- import manuel.doctest
- m = manuel.ignore.Manuel()
- m += manuel.codeblock.Manuel()
- m += manuel.doctest.Manuel()
-
-If we add an ignore marker to the block we don't want processed...
-
-.. code-block:: python
-
- The following is invalid Python.
-
- .. ignore-next-block
- .. code-block:: python
-
- def foo:
- pass
-
-.. -> source
-
- >>> document = manuel.Document(source)
-
-...the error goes away.
-
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
-
-
-Ignoring Literal Blocks
------------------------
-
-Ignoring literal blocks is a little more involved::
-
- Here is some invalid Python:
-
- .. ignore-next-block
-
- ::
-
- >>> lambda: print 'hi'
-
-.. -> source
-
- >>> document = manuel.Document(source)
-
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
-
-.. we want to be very sure that the above example without the ignore actually
- generates an error:
-
- >>> document = manuel.Document(document.source.replace(
- ... '.. ignore-next-block', ''))
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
- File "<memory>"...
- Failed example:
- lambda: print 'hi'
- Exception raised:
- ...
- File "<doctest <memory>[0]>", line 1
- lambda: print 'hi'
- ^
- SyntaxError: invalid syntax
-
-
-.. reset-globs
-.. _isolation:
-
-Test Isolation
-==============
-
-One of the advantages of unittest over doctest is that the individual tests are
-isolated from one-another.
-
-In large doctests (like this one) you may want to keep later tests from
-depending on incidental details of earlier tests, preventing the tests from
-becoming brittle and harder to change.
-
-Test isolation is one approach to reducing this intra-doctest coupling. The
-:mod:`manuel.isolation` module provides a plug-in to help.
-
-The ".. reset-globs" directive resets the globals in the test::
-
- We define a variable.
-
- >>> x = 'hello'
-
- It is still defined.
-
- >>> print x
- hello
-
- Now we can reset the globals...
-
- .. reset-globs
-
- ...and the name binding will be gone:
-
- >>> print x
- Traceback (most recent call last):
- ...
- NameError: name 'x' is not defined
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> import manuel.isolation
- >>> import manuel.doctest
- >>> m = manuel.isolation.Manuel()
- >>> m += manuel.doctest.Manuel()
-
-We can see that after the globals have been reset, the second "print x" line
-raises an error.
-
-Of course, resetting to an empty set of global variables isn't always what's
-wanted. In that case there is a ".. capture-globs" directive that saves a
-baseline set of globals that will be restored at each reset.
-
-::
-
- We define a variable.
-
- >>> x = 'hello'
-
- It is still defined.
-
- >>> print x
- hello
-
- We can capture the currently defined globals:
-
- .. capture-globs
-
- Of course capturing the globals doesn't disturb them.
-
- >>> print x
- hello
-
- Now if we define a new global...
-
- >>> y = 'goodbye'
- >>> print y
- goodbye
-
- .. reset-globs
-
- ...it will disappear after a reset.
-
- >>> print y
- Traceback (most recent call last):
- ...
- NameError: name 'y' is not defined
-
- But the captured globals will still be defined.
-
- >>> print x
- hello
-
-.. -> source
-
- >>> import manuel
- >>> document = manuel.Document(source)
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
-
-
-.. reset-globs
-.. _testcase:
-
-Identifying Test Cases
-======================
-
-If you want parts of a document to be individually accessible as test cases (to
-be able to run just a particular subset of them, for example), a parser can
-create a region that marks the beginning of a new test case.
-
-Two ways of identifying test cases are included in :mod:`manuel.testcase`:
-
-1. by section headings
-
-2. by explicit ".. test-case: NAME" markers.
-
-Grouping Tests by Heading
--------------------------
-
-::
-
- First Section
- =============
-
- Some prose.
-
- >>> print 'first test case'
-
- Some more prose.
-
- >>> print 'still in the first test case'
-
- Second Section
- ==============
-
- Even more prose.
-
- >>> print 'second test case'
-
-.. -> source
-
- >>> import manuel
- >>> import manuel.testcase
- >>> document = manuel.Document(source)
- >>> m = manuel.testcase.SectionManuel()
- >>> m += manuel.doctest.Manuel()
- >>> document.process_with(m, globs={})
- >>> print document.formatted(),
- File "<memory>"...
- Failed example:
- print 'first test case'
- Expected nothing
- Got:
- first test case
- File "<memory>"...
- Failed example:
- print 'still in the first test case'
- Expected nothing
- Got:
- still in the first test case
- File "<memory>"...
- Failed example:
- print 'second test case'
- Expected nothing
- Got:
- second test case
-
-.. now lets see if the regions are grouped as we expect
-
- >>> import manuel.testing
- >>> for regions in manuel.testing.group_regions_by_test_case(document):
- ... print (regions.location, regions.id)
- ('<memory>', None)
- ('<memory>', 'First Section')
- ('<memory>', 'Second Section')
-
-Given the above document, if you're using zope.testing's testrunner (located in bin/test), you could run just the tests in the second section with this command::
-
- bin/test -t "file-name.txt:Second Section"
-
-Or, exploiting the fact that -t does a regex search (as opposed to a match)::
-
- bin/test -t file-name.txt:Second
-
-
-Grouping Tests Explicitly
--------------------------
-
-If you would like to identify test cases separately from sections, you can
-identify them with a marker::
-
- First Section
- =============
-
- The following test will be in a test case that is not individually
- identifiable.
-
- >>> print 'first test case (unidentified)'
-
- Some more prose.
-
- .. test-case: first-named-test-case
-
- >>> print 'first identified test case'
-
-
- Second Section
- ==============
-
- The test case markers don't have to immediately proceed a test.
-
- .. test-case: second-named-test-case
-
- Even more prose.
-
- >>> print 'second identified test case'
-
-.. -> source
-
- >>> document = manuel.Document(source)
- >>> m = manuel.testcase.MarkerManuel()
- >>> m += manuel.doctest.Manuel()
- >>> document.parse_with(m)
- >>> for regions in manuel.testing.group_regions_by_test_case(document):
- ... print regions.location, regions.id
- <memory> None
- <memory> first-named-test-case
- <memory> second-named-test-case
-
-Again, given the above document and zope.testing, you could run just the second
-set of tests with this command::
-
- bin/test -t file-name.txt:second-named-test-case
-
-Or, exploiting the fact that -t does a regex search again::
-
- bin/test -t file-name.txt:second
-
-Even though the tests are individually accessable doesn't mean that they can't
-all be run at the same time::
-
- bin/test -t file-name.txt
-
-Also, if you create a hierarchy of names, you can run groups of tests at a
-time. For example, lets say that you append "-important" to all your really
-important tests, you could then run the important tests for a single document
-like so::
-
- bin/test -t 'file-name.txt:.*-important$'
-
-or all the "important" tests no matter what file they are in::
-
- bin/test -t '-important$'
-
-Both Methods
-------------
-
-You can also combine more than one test case identification method if you want.
-Here's an example of building a Manuel stack that has doctests and both flavors
-of test case identification:
-
-.. code-block:: python
-
- import manuel.doctest
- import manuel.testcase
-
- m = manuel.doctest.Manuel()
- m += manuel.testcase.SectionManuel()
- m += manuel.testcase.MarkerManuel()
-
-.. make sure above finds all the test cases appropriately
-
- >>> document.parse_with(m)
- >>> for regions in manuel.testing.group_regions_by_test_case(document):
- ... print regions.location, regions.id
- <memory> None
- <memory> First Section
- <memory> first-named-test-case
- <memory> Second Section
- <memory> second-named-test-case
-
-
-Further Reading
-===============
-
-.. toctree::
- :maxdepth: 1
-
- manuel/README.txt
- manuel/table-example.txt
- manuel/bugs.txt
Modified: manuel/trunk/src/manuel/README.txt
===================================================================
--- manuel/trunk/src/manuel/README.txt 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/README.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -72,7 +72,7 @@
... )[0]
>>> region.lineno
2
- >>> print region.source
+ >>> six.print_(region.source)
one: 1, 2, 3
two: 4, 5, 7
three: 3, 5, 1
@@ -109,8 +109,8 @@
>>> def parse(document):
... for region in document.find_regions(numbers_test_finder):
... description = region.start_match.group('description')
- ... numbers = map(
- ... int, region.start_match.group('numbers').split(','))
+ ... numbers = list(map(
+ ... int, region.start_match.group('numbers').split(',')))
... test = NumbersTest(description, numbers)
... document.claim_region(region)
... region.parsed = test
@@ -221,17 +221,17 @@
>>> m = manuel.doctest.Manuel()
>>> document.parse_with(m)
>>> for region in document:
- ... print (region.lineno, region.parsed or region.source)
+ ... print((region.lineno, region.parsed or region.source))
(1, 'This is my\ndoctest.\n\n')
- (4, <doctest.Example instance at 0x...>)
+ (4, <doctest.Example ...>)
Now we can evaluate the examples.
>>> document.evaluate_with(m, globs={})
>>> for region in document:
- ... print (region.lineno, region.evaluated or region.source)
+ ... print((region.lineno, region.evaluated or region.source))
(1, 'This is my\ndoctest.\n\n')
- (4, <manuel.doctest.DocTestResult instance at 0x...>)
+ (4, <manuel.doctest.DocTestResult ...>)
And format the results.
@@ -250,7 +250,7 @@
... 42
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
File "<memory>", line 4, in <memory>
Failed example:
1 + 1
@@ -287,7 +287,7 @@
... 42
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
File "<memory>", line 4, in <memory>
Failed example:
1 + 1
@@ -319,7 +319,7 @@
...
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
Globals
-------
@@ -338,7 +338,7 @@
... 1
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
Imported modules are added to the global namespace as well.
@@ -352,7 +352,7 @@
...
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
Combining Test Types
@@ -393,19 +393,19 @@
number tests.
>>> for region in document:
- ... print (region.lineno, region.parsed or region.source)
+ ... print((region.lineno, region.parsed or region.source))
(1, '\nWe can have a list of numbers...\n\n')
(4, <NumbersTest object at 0x...>)
(5, '\n... and we can test Python.\n\n')
- (8, <doctest.Example instance at 0x...>)
+ (8, <doctest.Example ...>)
(10, '\n')
We can look at the formatted output to see that each of the two tests failed.
>>> for region in document:
... if region.formatted:
- ... print '-'*70
- ... print region.formatted,
+ ... six.print_('-'*70)
+ ... six.print_(region.formatted, end='')
----------------------------------------------------------------------
the numbers aren't in sorted order: 3, 6, 2
----------------------------------------------------------------------
@@ -438,7 +438,7 @@
... if region.parsed:
... continue
... if region.source.strip().endswith('my clone:'):
- ... to_be_cloned = document_iter.next().copy()
+ ... to_be_cloned = six.advance_iterator(document_iter).copy()
... break
... # if we found the region to cloned, do so
... if to_be_cloned:
@@ -529,7 +529,7 @@
information.
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
File "<memory>", line 10, in <memory>
Failed example:
a + b
@@ -560,7 +560,7 @@
... """)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
File "<memory>", line 10, in <memory>
Failed example:
a + b # doesn't mention "c"
@@ -576,7 +576,7 @@
Instead of a text-based apprach, let's use the built-in tokenize module to more
robustly identify referenced variables.
- >>> import StringIO
+ >>> from six import StringIO
>>> import token
>>> import tokenize
@@ -586,7 +586,7 @@
...
... if region.evaluated.getvalue():
... vars = set()
- ... reader = StringIO.StringIO(region.source).readline
+ ... reader = StringIO(region.source).readline
... for ttype, tval, _, _, _ in tokenize.generate_tokens(reader):
... if ttype == token.NAME:
... vars.add(tval)
@@ -608,7 +608,7 @@
>>> document = manuel.Document(document.source)
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
File "<memory>", line 10, in <memory>
Failed example:
a + b # doesn't mention "c"
Modified: manuel/trunk/src/manuel/__init__.py
===================================================================
--- manuel/trunk/src/manuel/__init__.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/__init__.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -25,7 +25,7 @@
module_path = normalize_module_path(imp.find_module(name)[1])
# don't create a new module object if there's already one that we can reuse
- for module in sys.modules.values():
+ for module in list(sys.modules.values()):
if module is None or not hasattr(module, '__file__'):
continue
if module_path == normalize_module_path(module.__file__):
@@ -179,7 +179,7 @@
"""
return iter(self.regions)
- def __nonzero__(self):
+ def __bool__(self):
return bool(self.regions)
@@ -198,7 +198,7 @@
def find_regions(self, start, end=None):
def compile(regex):
- if regex is not None and isinstance(regex, basestring):
+ if regex is not None and isinstance(regex, str):
regex = re.compile(regex)
return regex
@@ -314,6 +314,10 @@
self.insert_region('after', marker_region, new_region)
+def call(func):
+ return func()
+
+
class Manuel(object):
_debug = False
@@ -354,7 +358,7 @@
self.formatters.extend(other.formatters)
# the testing integration (manuel.testing) sets this flag when needed
- @apply
+ @call
def debug():
def getter(self):
debug = self._debug
Modified: manuel/trunk/src/manuel/bugs.txt
===================================================================
--- manuel/trunk/src/manuel/bugs.txt 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/bugs.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -83,7 +83,7 @@
The doctest in the `source` variable ran with no errors.
- >>> print document.formatted()
+ >>> six.print_(document.formatted())
And now the globs dictionary reflects the changes made when the doctest ran.
@@ -142,7 +142,7 @@
The doctest in the `source` variable ran with no errors.
- >>> print document.formatted()
+ >>> six.print_(document.formatted())
We should clean up now.
@@ -192,5 +192,5 @@
>>> from manuel.testing import TestCase
>>> m = manuel.Manuel()
- >>> print TestCase(m, manuel.RegionContainer(), None).id()
+ >>> six.print_(TestCase(m, manuel.RegionContainer(), None).id())
<memory>
Modified: manuel/trunk/src/manuel/codeblock.py
===================================================================
--- manuel/trunk/src/manuel/codeblock.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/codeblock.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -27,7 +27,7 @@
if not isinstance(region.parsed, CodeBlock):
return
- exec region.parsed.code in globs
+ exec(region.parsed.code, globs)
del globs['__builtins__'] # exec adds __builtins__, we don't want it
Modified: manuel/trunk/src/manuel/doctest.py
===================================================================
--- manuel/trunk/src/manuel/doctest.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/doctest.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,11 +1,11 @@
-import StringIO
+import six
import manuel
import os.path
doctest = manuel.absolute_import('doctest')
-class DocTestResult(StringIO.StringIO):
+class DocTestResult(six.StringIO):
pass
@@ -17,7 +17,7 @@
region_end = region.lineno + region.source.count('\n')
for chunk in parser.parse(region.source):
# If the chunk contains prose (as opposed to and example), skip it.
- if isinstance(chunk, basestring):
+ if isinstance(chunk, str):
continue
chunk._manual = m
Copied: manuel/trunk/src/manuel/index.txt (from rev 125147, manuel/branches/benji-python3/src/manuel/index.txt)
===================================================================
--- manuel/trunk/src/manuel/index.txt (rev 0)
+++ manuel/trunk/src/manuel/index.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -0,0 +1,995 @@
+======
+Manuel
+======
+
+Manuel lets you mix and match traditional doctests with custom test syntax.
+
+Several plug-ins are included that provide new test syntax (see
+:ref:`functionality`). You can also create your own plug-ins.
+
+For example, if you've ever wanted to include a large chunk of Python in a
+doctest but were irritated by all the ">>>" and "..." prompts required, you'd
+like the :mod:`manuel.codeblock` module. It lets you execute code using
+Sphinx-style ".. code-block:: python" directives. The markup looks like
+this::
+
+ .. code-block:: python
+
+ import foo
+
+ def my_func(bar):
+ return foo.baz(bar)
+
+Incidentally, the implementation of :mod:`manuel.codeblock` is only 23 lines of
+code.
+
+The plug-ins included in Manuel make good examples while being quite useful in
+their own right. The Manuel documentation makes extensive use of them as well.
+Follow the "Show Source" link to the left to see the `reST
+<http://docutils.sourceforge.net/rst.html>`_ source of this document.
+
+For a large example of creating test syntax, take a look at the
+:ref:`fit-table-example` or for all the details, :ref:`theory-of-operation`.
+
+To see how to get Manuel wired up see :ref:`getting-started`.
+
+.. contents::
+
+
+.. reset-globs
+.. _functionality:
+
+Included Functionality
+======================
+
+Manuel includes several plug-ins out of the box:
+
+:ref:`manuel.capture <capture>`
+ stores regions of a document in variables for later processing
+
+:ref:`manuel.codeblock <code-blocks>`
+ executes code in ".. code-block:: python" blocks
+
+:ref:`manuel.doctest <doctest>`
+ provides traditional doctest processing as a Manuel plug-in
+
+:ref:`manuel.footnote <footnotes>`
+ executes code in reST-style footnodes each time they're referenced (good
+ for getting incidental code out of the main flow of a document)
+
+:ref:`manuel.ignore <ignore>`
+ ignores parts of a document while running tests
+
+:ref:`manuel.isolation <isolation>`
+ makes it easier to have test isolation in doctests
+
+:ref:`manuel.testcase <testcase>`
+ identify parts of tests as individual test cases so they can be run
+ independently
+
+
+.. reset-globs
+.. _getting-started:
+
+Getting Started
+===============
+
+The plug-ins used for a test are composed together using the "+" operator.
+Let's say you wanted a test that used doctest syntax as well as footnotes. You
+would create a Manuel instance to use like this:
+
+.. code-block:: python
+
+ import manuel.doctest
+ import manuel.footnote
+
+ m = manuel.doctest.Manuel()
+ m += manuel.footnote.Manuel()
+
+You would then pass the Manuel instance to a :class:`manuel.testing.TestSuite`,
+including the names of documents you want to process:
+
+.. ignore-next-block
+.. code-block:: python
+
+ manuel.testing.TestSuite(m, 'test-one.txt', 'test-two.txt')
+
+
+Using unittest
+--------------
+
+The simplest way to get started with Manuel is to use :mod:`unittest` to run
+your tests:
+
+.. code-block:: python
+
+ import manuel.codeblock
+ import manuel.doctest
+ import manuel.testing
+ import unittest
+
+ def test_suite():
+ m = manuel.doctest.Manuel()
+ m += manuel.codeblock.Manuel()
+ return manuel.testing.TestSuite(m, 'test-one.txt', 'test-two.txt')
+
+ if __name__ == '__main__':
+ unittest.TextTestRunner().run(test_suite())
+
+
+Using zope.testing
+------------------
+
+If you want to use a more featureful test runner you can use zope.testing's
+test runner (usable stand-alone -- it isn't dependent on the Zope application
+server). Create a file named :file:`tests.py` with a :func:`test_suite`
+function that returns a test suite.
+
+The suite can be either a :class:`manuel.testing.TestSuite` object or a
+:class:`unittest.TestSuite` as demonstrated below.
+
+.. code-block:: python
+
+ import manuel.codeblock
+ import manuel.doctest
+ import manuel.testing
+
+ def test_suite():
+ suite = unittest.TestSuite()
+
+ # here you add your other tests to the suite...
+
+ # now you can add the Manuel tests
+ m = manuel.doctest.Manuel()
+ m += manuel.codeblock.Manuel()
+ suite.addTest(manuel.testing.TestSuite(m,
+ 'test-one.txt', 'test-two.txt'))
+
+ return suite
+
+
+Others
+------
+
+If you know out how to make Manuel work with other test runners (nose, py.test,
+etc.), please `send me an email`_ and I'll expand this section.
+
+.. _send me an email: benji+manuel at benjiyork.com
+
+
+Customizing the TestCase class
+------------------------------
+
+Manuel has its own :class:`manuel.testing.TestClass` class that
+:class:`manuel.testing.TestSuite` uses. If you want to customize it, you
+can pass in your own class to `TestSuite`.
+
+.. code-block:: python
+
+ import os.path
+ import manuel.testing
+
+ class StripDirsTestCase(manuel.testing.TestCase):
+ def shortDescription(self):
+ return os.path.basename(str(self))
+ suite = manuel.testing.TestSuite(
+ m, path_to_test, TestCase=StripDirsTestCase)
+
+ >>> list(suite)[0].shortDescription()
+ 'bugs.txt'
+
+
+.. reset-globs
+.. _doctest:
+
+Doctests
+========
+
+Manuel is all about making testable documents and well-documented tests. Of
+course, Python's doctest module is a long-standing fixture in that space, so it
+only makes sense for Manuel to support doctest syntax.
+
+Handling doctests is easy:
+
+.. ignore-next-block
+.. code-block:: python
+
+ import manuel.doctest
+
+ m = manuel.doctest.Manuel()
+ suite = manuel.testing.TestSuite(m, 'my-doctest.txt')
+
+Of course you can mix in other Manuel syntax plug-ins as well (including ones
+you write yourself).
+
+.. ignore-next-block
+.. code-block:: python
+
+ import manuel.doctest
+ import manuel.codeblock
+
+ m = manuel.doctest.Manuel()
+ m += manuel.codeblock.Manuel()
+ suite = manuel.testing.TestSuite(m, 'my-doctest-with-code-blocks.txt')
+
+The :class:`manuel.doctest.Manuel` constructor also takes :data:`optionflags`
+and :data:`checker` arguments.
+
+.. ignore-next-block
+.. code-block:: python
+
+ m = manuel.doctest.Manuel(optionflags=optionflags, checker=checker)
+
+See the `doctest documentation <http://docs.python.org/library/doctest.html>`_
+for more information about the `available options
+<http://docs.python.org/library/doctest.html#doctest-options>`_ and `output
+checkers <http://docs.python.org/library/doctest.html#outputchecker-objects>`_
+
+
+.. note::
+
+ :mod:`zope.testing.renormalizing` provides an :class:`OutputChecker`
+ for smoothing out differences between actual and expected output for things
+ that are hard to control (like memory addresses and time). See the
+ `module's doctests <http://svn.zope.org/zope.testing/trunk/src/zope/testing/renormalizing.py?view=markup>`_
+ for more information on how it works.
+
+
+.. reset-globs
+.. _capture:
+
+Capturing Blocks
+================
+
+When writing documentation the need often arises to describe the contents of
+files or other non-Python information. You may also want to put that
+information under test. :mod:`manuel.capture` helps with that.
+
+For example, if you were writing the problems for a programming contest, you
+might want to describe the input and output files for each challenge, but you
+want to be sure that your examples are correct.
+
+To do that you might write your document like this:
+
+::
+
+ Challenge 1
+ ===========
+
+ Write a program that sorts the numbers in a file.
+
+
+ Example
+ -------
+
+ Given this example input file::
+
+ 6
+ 1
+ 8
+ 20
+ 11
+ 65
+ 2
+
+ .. -> input
+
+ Your program should generate this output file::
+
+ 1
+ 2
+ 6
+ 8
+ 11
+ 20
+ 65
+
+ .. -> output
+
+ >>> input_lines = input.splitlines()
+ >>> correct = '\n'.join(map(str, sorted(map(int, input_lines)))) + '\n'
+ >>> output == correct
+ True
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> import manuel.capture
+ >>> m = manuel.capture.Manuel()
+ >>> import manuel.doctest
+ >>> m += manuel.doctest.Manuel()
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+This uses the syntax implemented in :mod:`manuel.capture` to capture a block of
+text into a variable (the one named after "->").
+
+Whenever a line of the structure ".. -> VAR" is detected, the text of the
+*previous* block will be stored in the given variable.
+
+.. the paragraph below could be phrased better
+
+Of course, lines that start with ".. " are reST comments, so when the document
+is rendered with docutils or Sphinx, the tests will dissapear and only the
+intended document contents will remain. Like so::
+
+ Challenge 1
+ ===========
+
+ Write a program that sorts the numbers in a file.
+
+
+ Example
+ -------
+
+ Given this example input file::
+
+ 6
+ 1
+ 8
+ 20
+ 11
+ 65
+ 2
+
+ Your program should generate this output file::
+
+ 1
+ 2
+ 6
+ 8
+ 11
+ 20
+ 65
+
+
+.. reset-globs
+.. _code-blocks:
+
+Code Blocks
+===========
+
+`Sphinx <http://sphinx.pocoo.org/>`_ and other docutils `extensions
+<http://docutils.sourceforge.net/sandbox/code-block-directive/docs/syntax-highlight.html>`_
+provide a `"code-block" directive <http://sphinx.pocoo.org/markup/code.html>`_,
+which allows inlined snippets of code in reST documents.
+
+The :mod:`manuel.codeblock` module provides the ability to execute the contents
+of Python code-blocks. For example::
+
+ .. code-block:: python
+
+ print('hello')
+
+.. Let's create a reST document with a code block.
+
+ >>> import manuel.codeblock
+ >>> document = manuel.Document("""
+ ... Here is a code-block:
+ ...
+ ... .. code-block:: python
+ ...
+ ... x = 'hello'
+ ...
+ ... A little prose to separate the examples.
+ ...
+ ... >>> print(x)
+ ... hello
+ ...
+ ... """)
+
+.. Since the above document mixes code-blocks and doctests, we'll mix in the
+ doctest handler.
+
+ >>> import manuel.doctest
+ >>> m = manuel.codeblock.Manuel()
+ >>> m += manuel.doctest.Manuel()
+ >>> document.process_with(m, globs={})
+
+ Both code blocks were found (for a total of five regions -- text, block,
+ text, block, and text):
+
+ >>> len(list(document))
+ 5
+
+ We can see that none of the tests in the document failed:
+
+ >>> print(document.formatted())
+
+If the code-block generates some sort of error...
+
+.. code-block:: python
+
+ .. code-block:: python
+
+ print(does_not_exist)
+
+.. -> source
+
+ >>> document = manuel.Document(source, location='fake.txt')
+
+.. the document above was specially formulated to have nothing before or after
+ the code-block
+
+ >>> document.source.startswith('.. code-block')
+ True
+ >>> document.source.endswith('print(does_not_exist)\n')
+ True
+
+...that error will be reported:
+
+ >>> document.process_with(m, globs={})
+ Traceback (most recent call last):
+ ...
+ NameError: name 'does_not_exist' is not defined
+
+If you find that you want to include a code-block in a document but don't want
+Manuel to execute it, use :ref:`manuel.ignore <ignore>` to ignore that
+particular block.
+
+
+Invisible Code Blocks
+---------------------
+
+At times you'll want to have a block of code that is executed but not displayed
+in the rendered document (like some setup for later examples).
+
+When using doctest's native format (">>>") that's easy to do, you just put the
+code in a reST comment, like so:
+
+::
+
+ .. this is some setup, it is hidden in a reST comment
+
+ >>> a = 5
+ >>> b = a + 3
+
+However, if you want to include a relatively large chunk of Python, you'd
+rather use a code-block, but that means that it will be included in the
+rendered document. Instead, :mod:`manuel.codeblock` also understands a variant
+of the code-block directive that is actually a reST comment: "..
+invisible-code-block:: python"::
+
+ .. invisible-code-block:: python
+
+ a = 5
+ b = a + 3
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+.. note:: The "invisible-code-block" directive will work with either one or two
+ colons. The reason is that reST processers (like docutils and Sphinx) will
+ generate an error for unrecognized directives (like invisible-code-block).
+ Therefore you can use a single colon and the line will be interpreted as a
+ comment instead.
+
+.. the single-colon variant works too
+
+ >>> document = manuel.Document("""
+ ...
+ ... .. invisible-code-block: python
+ ...
+ ... raise RuntimeError('it worked!')
+ ...
+ ... """)
+ >>> document.process_with(m, globs={})
+ Traceback (most recent call last):
+ ...
+ RuntimeError: it worked!
+
+
+.. reset-globs
+.. _footnotes:
+
+Footnotes
+=========
+
+The :mod:`manuel.footnote` module provides an implementation of reST footnote
+handling, but instead of just plain text, the footnotes can contain any syntax
+Manuel can interpret including doctests.
+
+ >>> import manuel.footnote
+ >>> m = manuel.footnote.Manuel()
+
+Here's an example of combining footnotes with doctests:
+
+.. so we also need the doctest Manuel plug-in
+
+ >>> import manuel.doctest
+ >>> m += manuel.doctest.Manuel()
+
+::
+
+ Here we reference a footnote. [1]_
+
+ >>> x
+ 42
+
+ Here we reference another. [2]_
+
+ >>> x
+ 100
+
+ .. [1] This is a test footnote definition.
+
+ >>> x = 42
+
+ .. [2] This is another test footnote definition.
+
+ >>> x = 100
+
+ .. [3] This is a footnote that will never be executed.
+
+ >>> raise RuntimeError('nooooo!')
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+.. The order of examples in footnotes is preserved. If not, the document below
+ would generate an error because "a" won't be defined when "b = a + 1" is
+ evaluated.
+
+ >>> document = manuel.Document("""
+ ... Here we want some imports to be done. [foo]_
+ ...
+ ... >>> a + b
+ ... 3
+ ...
+ ... A little prose to separate the examples.
+ ...
+ ... .. [foo] Do something
+ ...
+ ... >>> a = 1
+ ...
+ ... >>> b = a + 1
+ ...
+ ... """)
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+It is also possible to reference more than one footnote on a single line.
+
+::
+
+ This line has several footnotes on it. [1]_ [2]_ [3]_
+
+ >>> z
+ 105
+
+ A little prose to separate the examples.
+
+ .. [1] Do something
+
+ >>> w = 3
+
+ .. [2] Do something
+
+ >>> x = 5
+
+ .. [3] Do something
+
+ >>> y = 7
+
+ >>> z = w * x * y
+
+.. -> source2
+
+ >>> document = manuel.Document(source)
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+
+.. reset-globs
+.. _ignore:
+
+Ignoring Blocks
+===============
+
+.. reset-globs
+
+Occasionally the need arises to ignore a block of markup that would otherwise
+be parsed by a Manuel plug-in.
+
+For example, this document has a code-block that will generate a syntax error::
+
+ The following is invalid Python.
+
+ .. code-block:: python
+
+ def foo:
+ pass
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> import manuel.codeblock
+ >>> m = manuel.codeblock.Manuel()
+
+We can see that when executed, the SyntaxError escapes.
+
+ >>> import manuel.codeblock
+ >>> m = manuel.codeblock.Manuel()
+ >>> document.process_with(m, globs={})
+ Traceback (most recent call last):
+ ...
+ File "<memory>:4", line 2
+ def foo:
+ ^
+ SyntaxError: invalid syntax
+
+The :mod:`manuel.ignore` module provides a way to ignore parts of a document
+using a directive ".. ignore-next-block".
+
+Because Manuel plug-ins are executed in the order they are accumulated, we want
+:mod:`manuel.ignore` to be the base Manuel object, with any additional plug-ins
+added to it.
+
+.. code-block:: python
+
+ import manuel.ignore
+ import manuel.doctest
+ m = manuel.ignore.Manuel()
+ m += manuel.codeblock.Manuel()
+ m += manuel.doctest.Manuel()
+
+If we add an ignore marker to the block we don't want processed...
+
+.. code-block:: python
+
+ The following is invalid Python.
+
+ .. ignore-next-block
+ .. code-block:: python
+
+ def foo:
+ pass
+
+.. -> source
+
+ >>> document = manuel.Document(source)
+
+...the error goes away.
+
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+
+Ignoring Literal Blocks
+-----------------------
+
+Ignoring literal blocks is a little more involved::
+
+ Here is some invalid Python:
+
+ .. ignore-next-block
+
+ ::
+
+ >>> lambda: x=1
+
+.. -> source
+
+ >>> document = manuel.Document(source)
+
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+.. we want to be very sure that the above example without the ignore actually
+ generates an error:
+
+ >>> document = manuel.Document(document.source.replace(
+ ... '.. ignore-next-block', ''))
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+ File "<memory>"...
+ Exception raised:
+ ...
+ SyntaxError: ...
+
+
+.. reset-globs
+.. _isolation:
+
+Test Isolation
+==============
+
+One of the advantages of unittest over doctest is that the individual tests are
+isolated from one-another.
+
+In large doctests (like this one) you may want to keep later tests from
+depending on incidental details of earlier tests, preventing the tests from
+becoming brittle and harder to change.
+
+Test isolation is one approach to reducing this intra-doctest coupling. The
+:mod:`manuel.isolation` module provides a plug-in to help.
+
+The ".. reset-globs" directive resets the globals in the test::
+
+ We define a variable.
+
+ >>> x = 'hello'
+
+ It is still defined.
+
+ >>> print(x)
+ hello
+
+ Now we can reset the globals...
+
+ .. reset-globs
+
+ ...and the name binding will be gone:
+
+ >>> print(x)
+ Traceback (most recent call last):
+ ...
+ NameError: name 'x' is not defined
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> import manuel.isolation
+ >>> import manuel.doctest
+ >>> m = manuel.isolation.Manuel()
+ >>> m += manuel.doctest.Manuel()
+
+We can see that after the globals have been reset, the second "print(x)" line
+raises an error.
+
+Of course, resetting to an empty set of global variables isn't always what's
+wanted. In that case there is a ".. capture-globs" directive that saves a
+baseline set of globals that will be restored at each reset.
+
+::
+
+ We define a variable.
+
+ >>> x = 'hello'
+
+ It is still defined.
+
+ >>> print(x)
+ hello
+
+ We can capture the currently defined globals:
+
+ .. capture-globs
+
+ Of course capturing the globals doesn't disturb them.
+
+ >>> print(x)
+ hello
+
+ Now if we define a new global...
+
+ >>> y = 'goodbye'
+ >>> print(y)
+ goodbye
+
+ .. reset-globs
+
+ ...it will disappear after a reset.
+
+ >>> print(y)
+ Traceback (most recent call last):
+ ...
+ NameError: name 'y' is not defined
+
+ But the captured globals will still be defined.
+
+ >>> print(x)
+ hello
+
+.. -> source
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+
+
+.. reset-globs
+.. _testcase:
+
+Identifying Test Cases
+======================
+
+If you want parts of a document to be individually accessible as test cases (to
+be able to run just a particular subset of them, for example), a parser can
+create a region that marks the beginning of a new test case.
+
+Two ways of identifying test cases are included in :mod:`manuel.testcase`:
+
+1. by section headings
+
+2. by explicit ".. test-case: NAME" markers.
+
+Grouping Tests by Heading
+-------------------------
+
+::
+
+ First Section
+ =============
+
+ Some prose.
+
+ >>> print('first test case')
+
+ Some more prose.
+
+ >>> print('still in the first test case')
+
+ Second Section
+ ==============
+
+ Even more prose.
+
+ >>> print('second test case')
+
+.. -> source
+
+ >>> import manuel
+ >>> import manuel.testcase
+ >>> document = manuel.Document(source)
+ >>> m = manuel.testcase.SectionManuel()
+ >>> m += manuel.doctest.Manuel()
+ >>> document.process_with(m, globs={})
+ >>> print(document.formatted())
+ File "<memory>"...
+ Failed example:
+ print('first test case')
+ Expected nothing
+ Got:
+ first test case
+ File "<memory>"...
+ Failed example:
+ print('still in the first test case')
+ Expected nothing
+ Got:
+ still in the first test case
+ File "<memory>"...
+ Failed example:
+ print('second test case')
+ Expected nothing
+ Got:
+ second test case
+
+.. now lets see if the regions are grouped as we expect
+
+ >>> import manuel.testing
+ >>> for regions in manuel.testing.group_regions_by_test_case(document):
+ ... print((regions.location, regions.id))
+ ('<memory>', None)
+ ('<memory>', 'First Section')
+ ('<memory>', 'Second Section')
+
+Given the above document, if you're using zope.testing's testrunner (located in bin/test), you could run just the tests in the second section with this command::
+
+ bin/test -t "file-name.txt:Second Section"
+
+Or, exploiting the fact that -t does a regex search (as opposed to a match)::
+
+ bin/test -t file-name.txt:Second
+
+
+Grouping Tests Explicitly
+-------------------------
+
+If you would like to identify test cases separately from sections, you can
+identify them with a marker::
+
+ First Section
+ =============
+
+ The following test will be in a test case that is not individually
+ identifiable.
+
+ >>> print('first test case (unidentified)')
+
+ Some more prose.
+
+ .. test-case: first-named-test-case
+
+ >>> print('first identified test case')
+
+
+ Second Section
+ ==============
+
+ The test case markers don't have to immediately proceed a test.
+
+ .. test-case: second-named-test-case
+
+ Even more prose.
+
+ >>> print('second identified test case')
+
+.. -> source
+
+ >>> document = manuel.Document(source)
+ >>> m = manuel.testcase.MarkerManuel()
+ >>> m += manuel.doctest.Manuel()
+ >>> document.parse_with(m)
+ >>> import six
+ >>> for regions in manuel.testing.group_regions_by_test_case(document):
+ ... six.print_(regions.location, regions.id)
+ <memory> None
+ <memory> first-named-test-case
+ <memory> second-named-test-case
+
+Again, given the above document and zope.testing, you could run just the second
+set of tests with this command::
+
+ bin/test -t file-name.txt:second-named-test-case
+
+Or, exploiting the fact that -t does a regex search again::
+
+ bin/test -t file-name.txt:second
+
+Even though the tests are individually accessable doesn't mean that they can't
+all be run at the same time::
+
+ bin/test -t file-name.txt
+
+Also, if you create a hierarchy of names, you can run groups of tests at a
+time. For example, lets say that you append "-important" to all your really
+important tests, you could then run the important tests for a single document
+like so::
+
+ bin/test -t 'file-name.txt:.*-important$'
+
+or all the "important" tests no matter what file they are in::
+
+ bin/test -t '-important$'
+
+Both Methods
+------------
+
+You can also combine more than one test case identification method if you want.
+Here's an example of building a Manuel stack that has doctests and both flavors
+of test case identification:
+
+.. code-block:: python
+
+ import manuel.doctest
+ import manuel.testcase
+
+ m = manuel.doctest.Manuel()
+ m += manuel.testcase.SectionManuel()
+ m += manuel.testcase.MarkerManuel()
+
+.. make sure above finds all the test cases appropriately
+
+ >>> document.parse_with(m)
+ >>> import six
+ >>> for regions in manuel.testing.group_regions_by_test_case(document):
+ ... six.print_(regions.location, regions.id)
+ <memory> None
+ <memory> First Section
+ <memory> first-named-test-case
+ <memory> Second Section
+ <memory> second-named-test-case
+
+
+Further Reading
+===============
+
+.. toctree::
+ :maxdepth: 1
+
+ README.txt
+ table-example.txt
+ bugs.txt
Modified: manuel/trunk/src/manuel/table-example.txt
===================================================================
--- manuel/trunk/src/manuel/table-example.txt 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/table-example.txt 2012-04-16 19:07:44 UTC (rev 125148)
@@ -104,6 +104,7 @@
.. code-block:: python
import re
+ import six
table_start = re.compile(r'(?<=\n\n)=[= ]+\n(?=[ \t]*?\S)', re.DOTALL)
table_end = re.compile(r'\n=[= ]+\n(?=\Z|\n)', re.DOTALL)
@@ -111,17 +112,17 @@
def parse_tables(document):
for region in document.find_regions(table_start, table_end):
lines = enumerate(iter(region.source.splitlines()))
- lines.next() # skip the first line
+ six.advance_iterator(lines) # skip the first line
# grab the expression to be evaluated
- expression = lines.next()[1]
+ expression = six.advance_iterator(lines)[1]
if expression.startswith('\\'):
expression = expression[1:]
- lines.next() # skip the divider line
- variables = [v.strip() for v in lines.next()[1].split()][:-1]
+ six.advance_iterator(lines) # skip the divider line
+ variables = [v.strip() for v in six.advance_iterator(lines)[1].split()][:-1]
- lines.next() # skip the divider line
+ six.advance_iterator(lines) # skip the divider line
examples = []
for lineno_offset, line in lines:
@@ -142,7 +143,8 @@
>>> parse_tables(document)
>>> region = list(document)[1]
- >>> print region.source,
+ >>> import six
+ >>> six.print_(region.source, end='')
===== ===== ======
\ A or B
--------------------
@@ -278,7 +280,7 @@
We can see how the results are formatted.
>>> format_table_errors(document)
- >>> print region.formatted,
+ >>> six.print_(region.formatted, end='')
when evaluating table at fake.txt, line 6
fake.txt, line 11: expected True, got False instead.
fake.txt, line 13: expected False, got True instead.
@@ -303,7 +305,7 @@
>>> m = Manuel()
>>> document = manuel.Document(source_with_errors, location='fake.txt')
>>> document.process_with(m, globs={})
- >>> print document.formatted(),
+ >>> six.print_(document.formatted(), end='')
when evaluating table at fake.txt, line 6
fake.txt, line 11: expected True, got False instead.
fake.txt, line 13: expected False, got True instead.
@@ -312,7 +314,7 @@
>>> document = manuel.Document(source, location='fake.txt')
>>> document.process_with(m, globs={})
- >>> print document.formatted()
+ >>> six.print_(document.formatted())
If we wanted to use instances of our Manuel object in a test, we would follow
the directions in :ref:`getting-started`, importing Manuel from the module
Modified: manuel/trunk/src/manuel/testing.py
===================================================================
--- manuel/trunk/src/manuel/testing.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/testing.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,4 +1,4 @@
-import doctest
+from . import doctest
import inspect
import itertools
import manuel
@@ -70,7 +70,7 @@
while True:
region = None # being defensive
try:
- region = document_iter.next()
+ region = next(document_iter)
except StopIteration:
if not accumulated_regions:
break
@@ -94,7 +94,7 @@
# if there are no more regions, stop
try:
- region = document_iter.next()
+ region = next(document_iter)
except StopIteration:
break
Modified: manuel/trunk/src/manuel/tests.py
===================================================================
--- manuel/trunk/src/manuel/tests.py 2012-04-16 19:03:55 UTC (rev 125147)
+++ manuel/trunk/src/manuel/tests.py 2012-04-16 19:07:44 UTC (rev 125148)
@@ -1,4 +1,4 @@
-import doctest
+from . import doctest
import manuel
import manuel.capture
import manuel.codeblock
@@ -32,7 +32,7 @@
... 5
... ''')
>>> document.process_with(manuel.doctest.Manuel(), globs={})
- >>> print document.formatted()
+ >>> print(document.formatted())
File "<memory>", line 3, in <memory>
Failed example:
2 + 2
@@ -46,7 +46,7 @@
def test_suite():
- tests = ['../index.txt', 'table-example.txt', 'README.txt', 'bugs.txt',
+ tests = ['index.txt', 'table-example.txt', 'README.txt', 'bugs.txt',
'capture.txt']
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
More information about the checkins
mailing list