Skip to content

Commit

Permalink
initial commit, got console stuff working but its still a long way to
Browse files Browse the repository at this point in the history
first actual release
  • Loading branch information
garbas committed Sep 2, 2010
0 parents commit 5664573
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 0 deletions.
58 changes: 58 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
`Sphinx`_ is one freaking great tool to write documentation. Not just python
documentation, all kinds of documentation. And there is a big reason why to
write documentation in `Sphinx`_. You can write code which will be nicely
formated for you with all the shines.

.. contents

What happens if you want to test your documentation?
----------------------------------------------------

Well not much. There is a build-in plugin `sphinx.ext.docstest`_ which enables
you to do some testing, but is limited to only python. And how to test all that
console commands? Using python? I am pythonista, but testing console commands
with ptyhon os.system or similar sounds wrong and anybody doing this will burn
in hell anyway.

So here is where `sphinx.testing` comes in. It will use all those `..
code-block:: ` and `.. sourcecode::` blocks you have laying around in your
documentation and run them for you, optionaly if you want it ofcourse. For now
we only support python and console type of blocks, but its easy to extend and
provide general test runner for any language that is actually supported by `..
code-block:`, which is basicaly what `Pygments`_ support (`list of lexers`_).


TODO: write how to use it

Credits
=======

* `Rok Garbas`_, author

* `Manuel`_, for inspiration and all the documentation i borrowed and implement it to be more tightly integrated with sphinx.
* `plone.testing`_, for pusing me to do this. once you taste the sweetness of layers you want to use them anywhere
* `mrsd`_, to be first package to use this SphinxTestSuite

Todo
====

* support for python and "python cosole" aka doctests


Changelog
=========

0.1 - 2010-09-01
----------------

* support for shell console code-block testing
* initial release


.. _`Sphinx`: http://sphinx.pocoo.org
.. _`sphinx.ext.doctest`: http://sphinx.pocoo.org/ext/doctest.html
.. _`Rok Garbas`: http://www.garbas.si
.. _`plone.testing`: http://pypi.python.org/pypi/plone.testing
.. _`mrsd`: http://pypi.python.org/pypi/mrsd
.. _`Manuel`: http://pypi.python.org/pypi/manuel
.. _`list of lexers`: http://pygments.org/docs/lexers
35 changes: 35 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from setuptools import setup, find_packages
import os

version = '0.1'

setup(name='sphinx.testing',
version=version,
description="",
long_description=open("README.txt").read(),
# Get more strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
"Programming Language :: Python",
],
keywords='',
author='',
author_email='',
url='http://svn.plone.org/svn/collective/',
license='GPL',
packages=find_packages(exclude=['ez_setup']),
namespace_packages=['sphinx'],
include_package_data=True,
zip_safe=False,
install_requires=[
'setuptools',
# -*- Extra requirements: -*-
],
extras_require={
'layer': [
'plone.testing',
]
},
entry_points="""
# -*- Entry points: -*-
""",
)
6 changes: 6 additions & 0 deletions sphinx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
2 changes: 2 additions & 0 deletions sphinx/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import directive
from suite import SphinxTestSuite
17 changes: 17 additions & 0 deletions sphinx/testing/case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import subprocess
import unittest2 as unittest


class ConsoleTestCase(unittest.TestCase):

def runTest(self):
for command, expected_result in self.commands:
p = subprocess.Popen(command.split(),
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
cwd=self.layer['buildout-directory'])
result, _ = p.communicate()
p.wait()
result = result.rstrip('\n')
self.assertEqual(expected_result, result)
50 changes: 50 additions & 0 deletions sphinx/testing/directive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import case
import pygments.token
import pygments.lexers
import docutils.parsers.rst

class CodeBlock(docutils.parsers.rst.Directive):

has_content = True
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
option_spec = dict(
layer=docutils.parsers.rst.directives.unchanged)

def run(self):
self.assert_has_content()
try:
lexer = pygments.lexers.get_lexer_by_name(self.arguments[0])
except Exception, e:
raise Exception('No appropriate lexer found for this '
'code-block: ' + self.arguments[0])

if self.arguments[0] == 'console':
test = case.ConsoleTestCase()
test.commands = []
test = self.prompt_parser(test, lexer)
else:
raise Exception('No testcase for code-block '
'found: ' + self.arguments[0])
return [docutils.nodes.raw(test, '')]

def prompt_parser(self, test, lexer):
command, expected_result = '', ''
for item in lexer.get_tokens(u'\n'.join(self.content)):
if item[0] == pygments.token.Token.Generic.Output:
expected_result += item[1]
elif item[0] not in [pygments.token.Token.Generic.Prompt,
pygments.token.Token.Literal.String.Escape,]:
command += item[1]
if command and \
item[0] == pygments.token.Token.Generic.Prompt and \
item[1] in ['#', '$', '%']: # FIXME: should be generic
test.commands.append((command.strip(),
expected_result.rstrip('\n')))
command, expected_result = '', ''
test.commands.append((command.strip(),
expected_result.rstrip('\n')))
return test

docutils.parsers.rst.directives.register_directive('code-block', CodeBlock)
64 changes: 64 additions & 0 deletions sphinx/testing/suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import unittest2 as unittest
import docutils.parsers.rst

try:
import plone.testing.layer as layer
layer_support = True
except:
layer_support = False


class SphinxTestSuite(unittest.TestSuite):

def __init__(self, tests, layer=None):
self.tests = []
self.parser = docutils.parsers.rst.Parser()
self.addTests(tests, layer)

def addTests(self, tests, default_layer):
for test in tests:
if isinstance(test, list) or isinstance(test, tuple):
if len(test) == 2 and layer_support and \
isinstance(test[1], layer.Layer):
self.addTest(test[0], test[1])
else:
raise Exception('Test should come in pair with layer.')
if default_layer is not None and layer_support and \
isinstance(default_layer, layer.Layer):
self.addTest(test, default_layer)
else:
self.addTest(test)

def addTest(self, test, layer=None):
if not isinstance(test, basestring):
raise Exception('Test should be path to existing file.')
if not os.path.exists(test):
raise Exception('File does not exists: ' + test)

test_file = os.path.abspath(test)
test_f = open(test_file)
rst_input = test_f.read()
test_f.close()

settings = docutils.frontend.OptionParser(
components=(self.parser,)).get_default_values()

rst_document = docutils.utils.new_document(test_file, settings)
try:
self.parser.parse(rst_input, rst_document)
except Exception, e:
pass


for test_block in rst_document.traverse():
test = test_block.rawsource
if isinstance(test, unittest.TestCase):
if layer is not None and \
not getattr(test, 'layer', False):
test.layer = layer
self.tests.append(test)

def __iter__(self):
for test in self.tests:
yield test

0 comments on commit 5664573

Please sign in to comment.