[Checkins] SVN: manuel/trunk/ Added simple support for Sphinx-style testsetup/testcode/testresult blocks.
Lennart Regebro
regebro at gmail.com
Sun Jan 3 06:29:51 EST 2010
Log message for revision 107581:
Added simple support for Sphinx-style testsetup/testcode/testresult blocks.
Changed:
U manuel/trunk/CHANGES.txt
U manuel/trunk/buildout.cfg
A manuel/trunk/src/manuel/sphinx.py
A manuel/trunk/src/manuel/sphinx.txt
U manuel/trunk/src/manuel/tests.py
-=-
Modified: manuel/trunk/CHANGES.txt
===================================================================
--- manuel/trunk/CHANGES.txt 2010-01-03 08:38:30 UTC (rev 107580)
+++ manuel/trunk/CHANGES.txt 2010-01-03 11:29:50 UTC (rev 107581)
@@ -6,7 +6,9 @@
- fix a small doc thinko
+- Added simple support for Sphinx-style testsetup/testcode/testresult blocks.
+
1.0.2 (2009-12-07)
------------------
Modified: manuel/trunk/buildout.cfg
===================================================================
--- manuel/trunk/buildout.cfg 2010-01-03 08:38:30 UTC (rev 107580)
+++ manuel/trunk/buildout.cfg 2010-01-03 11:29:50 UTC (rev 107581)
@@ -55,3 +55,4 @@
zc.recipe.testrunner = 1.2.0
zope.interface = 3.5.1
zope.testing = 3.7.5
+distribute = 0.6.10
\ No newline at end of file
Added: manuel/trunk/src/manuel/sphinx.py
===================================================================
--- manuel/trunk/src/manuel/sphinx.py (rev 0)
+++ manuel/trunk/src/manuel/sphinx.py 2010-01-03 11:29:50 UTC (rev 107581)
@@ -0,0 +1,118 @@
+import os
+import re
+import manuel
+import textwrap
+doctest = __import__('doctest')
+import manuel.doctest
+
+BLOCK_START = re.compile(r'^\.\. test(setup|code|output):: *(testgroup)?', re.MULTILINE)
+BLOCK_END = re.compile(r'(\n\Z|\n(?=\S))')
+
+# TODO: We need to handle groups, which we don't, and we need to handle that
+# for doctests as well, which we don't, and we probably need to handle
+# doctest options as well. And we might want to make sure that we are compatible
+# with how sphinx handles different cases, like groups and the '*' group and
+# setup and globals, etc.
+
+class TestSetup(object):
+ def __init__(self, code, group):
+ self.code = code
+ self.group = group
+
+class TestCode(object):
+ def __init__(self, code, group):
+ self.code = code
+ self.group = group
+
+class TestOutput(object):
+ def __init__(self, code, group):
+ self.code = code
+ self.group = group
+
+class TestCase(object):
+ def __init__(self, setup, code, output):
+ if setup:
+ self.code = setup.code + '\n' + code.code
+ else:
+ self.code = code.code
+ self.output = output.code
+ # When we start handling groups, we want to get the group of the
+ # test here, so we can put that in the test report, I think:
+ self.group = ''
+
+def parse(document):
+ groups = {}
+ previous_type = ''
+ for region in document.find_regions(BLOCK_START, BLOCK_END):
+ source = textwrap.dedent('\n'.join(region.source.splitlines()[1:]))
+ document.claim_region(region)
+ type_, group = region.start_match.groups()
+ if type_ == 'setup':
+ region.parsed = TestSetup(source, region.start_match.groups()[1])
+ elif type_ == 'code':
+ region.parsed = TestCode(source, region.start_match.groups()[1])
+ elif type_ == 'output':
+ region.parsed = TestOutput(source, region.start_match.groups()[1])
+
+ # Now go through the document and make all groups of regions into testcases.
+ #iterator = iter(document)
+ setup_region = None
+ code_region = None
+ for region in document:
+ if isinstance(region.parsed, TestSetup):
+ # This is a new block, finish the old:
+ if code_region:
+ code_region.parsed = TestCase(setup_region.parsed,
+ code_region.parsed,
+ None)
+ code_region = None
+ setup_region = region
+ elif isinstance(region.parsed, TestCode):
+ if code_region:
+ # This is a new block, yield the old:
+ code_region.parsed = TestCase(setup_region.parsed,
+ code_region.parsed,
+ None)
+ setup_region = None
+ code_region = region
+ elif isinstance(region.parsed, TestOutput):
+ # This is the end of a block
+ code_region.parsed = TestCase(setup_region.parsed,
+ code_region.parsed,
+ region.parsed)
+ setup_region, code_region = None, None
+
+def monkey_compile(code, name, type, flags, dont_inherit):
+ return compile(code, name, 'exec', flags, dont_inherit)
+doctest.compile = monkey_compile
+
+def evaluate(region, document, globs):
+ if not isinstance(region.parsed, TestCase):
+ return
+
+ result = manuel.doctest.DocTestResult()
+ if region.parsed.group:
+ test_name = region.parsed.group
+ else:
+ test_name = os.path.split(document.location)[1]
+
+ exc_msg = None
+ output = region.parsed.output
+ if output:
+ match = doctest.DocTestParser._EXCEPTION_RE.match(output)
+ if match:
+ exc_msg = match.group('msg')
+
+ example = doctest.Example(region.parsed.code, output, exc_msg=exc_msg,
+ lineno=region.lineno)
+ test = doctest.DocTest([example], globs, test_name, document.location,
+ region.lineno-1, None)
+ runner = doctest.DocTestRunner()
+ runner.DIVIDER = '' # disable unwanted result formatting
+ runner.run(test, clear_globs=False)
+ region.evaluated = result
+
+
+class Manuel(manuel.Manuel):
+ def __init__(self):
+ manuel.Manuel.__init__(self, [parse], [evaluate])
Added: manuel/trunk/src/manuel/sphinx.txt
===================================================================
--- manuel/trunk/src/manuel/sphinx.txt (rev 0)
+++ manuel/trunk/src/manuel/sphinx.txt 2010-01-03 11:29:50 UTC (rev 107581)
@@ -0,0 +1,66 @@
+Sphinx Testing
+==============
+
+Sphinx [http://sphinx.pocoo.org/] is a popular system to render Restructure
+text documents into various forms, including HTML and PDF. It has it's own
+syntax to run tests, more advanced that the Manuel syntax. It includes
+setup blocks, and expected output blocks. The code will be assumed to be
+Python.
+
+Code blocks for testing start with ``.. testcode ::``::
+
+ >>> source = """\
+ ... Code blocks for testing start with ``.. testcode ::``::
+ ...
+ ... .. testcode::
+ ...
+ ... print "Heybaberiba"
+ ...
+ ... """
+
+Now we can parse this document with manuel and the sphinx extension::
+
+ >>> import manuel
+ >>> import manuel.sphinx
+ >>> document = manuel.Document(source)
+ >>> manuel.sphinx.parse(document)
+ >>> for region in document:
+ ... print region.parsed
+ None
+ <manuel.sphinx.TestCode object at ...>
+
+The benefit over Sphinx "code-block" is that you can have setup blocks and
+output test blocks.
+
+ >>> source = """\
+ ... Code blocks for testing start with ``.. testcode ::``::
+ ...
+ ... .. testsetup::
+ ... lyrics = "Da doo ron ron"
+ ...
+ ... .. testcode::
+ ... print lyrics
+ ...
+ ... .. testoutput::
+ ... Da doo ron ron
+ ...
+ ... """
+
+ >>> import manuel
+ >>> document = manuel.Document(source)
+ >>> manuel.sphinx.parse(document)
+ >>> for region in document:
+ ... print region.parsed
+ None
+ <manuel.sphinx.TestSetup object at ...>
+ <manuel.sphinx.TestCase object at ...>
+ <manuel.sphinx.TestOutput object at ...>
+
+The evaluate method will run the code in the TesCase blocks, and compare the
+output with the output in the TestCase blocks. The TestSetup and TestOutput
+blocks are only used as intermediates during parsing, and ignored during
+evaluation.
+
+ >>> for region in document:
+ ... manuel.sphinx.evaluate(region, document, {})
+
Modified: manuel/trunk/src/manuel/tests.py
===================================================================
--- manuel/trunk/src/manuel/tests.py 2010-01-03 08:38:30 UTC (rev 107580)
+++ manuel/trunk/src/manuel/tests.py 2010-01-03 11:29:50 UTC (rev 107581)
@@ -32,7 +32,7 @@
])
tests = ['../index.txt', 'table-example.txt', 'README.txt', 'bugs.txt',
- 'capture.txt']
+ 'capture.txt', 'sphinx.txt']
m = manuel.ignore.Manuel()
m += manuel.doctest.Manuel(optionflags=optionflags, checker=checker)
More information about the checkins
mailing list