[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