pylit_test.py¶
Contents
A catalogue of errors¶
from file:///home/milde/Texte/Doc/Programmierung/Software-Carpentry/lec/unit.html
- Numbers: zero, largest, smallest magnitude, most negative
- Structures: empty, exactly one element, maximum number of elements - Duplicate elements (e.g., letter “J” appears three times in a string) - Aliased elements (e.g., a list contains two references to another list) - Circular structures (e.g., a list that contains a reference to itself)
- Searching: no match found, one match found, multiple matches found, everything matches - Code like x = find_all(structure)[0] is almost always wrong - Should also check aliased matches (same thing found multiple times)
"""pylit_test.py: test the "literal python" module"""
from pprint import pprint
import operator
import unittest
from functools import reduce
Be sure that we’re working with a development copy of PyLit.
import sys, os
sys.path.insert( 0, os.path.abspath("..") )
from pylit import *
Test DefaultDict¶
class Test_DefaultDict(unittest.TestCase):
"""Test the DefaultDict dictionary with custom default"""
def setUp(self):
self.defdict = DefaultDict(lambda:'#')
def test_get_default(self):
self.assertEqual( self.defdict['nonexisting'], '#' )
def test_set_get(self):
self.defdict['mykey'] = 3
self.assertEqual( self.defdict['mykey'], 3 )
def test_change_default(self):
self.defdict.default_factory = lambda:'%'
self.assertEqual( self.defdict['nonexisting'], '%' )
def test_init_args(self):
di = DefaultDict(lambda:'#', {'mykey': 3})
self.assertEqual( di['mykey'], 3 )
self.assertEqual( di['nonexisting'], '#' )
def test_init_args2(self):
di = DefaultDict(lambda:'#', mykey = 3)
self.assertEqual( di['mykey'], 3 )
self.assertEqual( di['nonexisting'], '#' )
def test_init_args3(self):
di = DefaultDict(lambda:'#', [('mykey', 3)])
self.assertEqual( di['mykey'], 3 )
self.assertEqual( di['nonexisting'], '#' )
Test strings¶
Example of text, code and stripped code with typical features”:
text = """.. #!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
Leading text
in several paragraphs followed by a literal block::
block1 = 'first block'
Some more text and the next block. ::
block2 = 'second block'
print( block1, block2 )
Trailing text.
"""
# print( text )
The converter expects the data in separate lines (iterator or list) with trailing newlines. We use the splitlines string method with keepends=True:
textdata = text.splitlines(True)
# print( textdata )
If a “code” source is converted with the strip option, only text blocks are extracted, which leads to:
stripped_text = """Leading text
in several paragraphs followed by a literal block:
Some more text and the next block.
Trailing text.
"""
The code corresponding to the text test string.
Using a triple-quoted string for the code (and stripped_code) can create
problems with the conversion of this test by pylit (as the text parts
would be converted to text).
A workaround is using a different comment string for the text blocks and
converting with e.g. pylit --comment-string='## ' pylit_test.py
.
code = """#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
# Leading text
#
# in several paragraphs followed by a literal block::
block1 = 'first block'
# Some more text and the next block. ::
block2 = 'second block'
print( block1, block2 )
# Trailing text.
"""
# print( code )
codedata = code.splitlines(True)
Converting the text teststring with the strip option leads to:
stripped_code = """#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
block1 = 'first block'
block2 = 'second block'
print( block1, block2 )
"""
pprint(textdata) pprint(stripped_code.splitlines(True))
Containers for special case examples:
1. Text2Code samples
textsamples["what"] = (<text data>, <output>, <output (with `strip`)
textsamples = {}
2. Code2Text samples
codesamples["what"] = (<code data>, <output>, <output (with `strip`)
codesamples = {}
TestCase class to test the textsamples and codesamples:
class Check_Converter( unittest.TestCase ):
def args( self, key, converter, output):
"""Configure this Check_Converter case."""
self.key= key
self.converter= converter
self.output= output
return self
def shortDescription( self ):
return self.key
def runTest(self):
"""Execute the test."""
print( "E:", self.key )
extract = self.converter()
print(extract)
outstr = "".join(extract)
print( "soll:", repr(self.output) )
print( "ist: ", repr(outstr) )
self.assertEqual( self.output, outstr )
Test generator for textsample tests:
def make_Text2Code_samples():
suite= unittest.TestSuite()
for key, sample in textsamples.items():
t= Check_Converter()
suite.addTest( t.args( "Text2Code " + key,
Text2Code(sample[0].splitlines(True)), sample[1]) )
if len(sample) == 3:
t= Check_Converter()
suite.addTest( t.args( "Text2Code " + key,
Text2Code(sample[0].splitlines(True), strip=True),
sample[2]) )
return suite
Test generator for codesample tests:
def make_Code2Text_samples():
suite= unittest.TestSuite()
for key, sample in codesamples.items():
t= Check_Converter()
suite.addTest( t.args( "Code2Text " + key,
Code2Text(sample[0].splitlines(True)), sample[1]) )
if len(sample) == 3:
t= Check_Converter()
suite.addTest( t.args( "Code2Text " + key,
Code2Text(sample[0].splitlines(True), strip=True),
sample[2]) )
return suite
Pre and postprocessing filters (for testing the filter hooks)
def r2l_filter(data):
print( "applying r2l filter" )
for line in data:
yield line.replace("r", "l")
defaults.preprocessors["rl2text"] = r2l_filter
def l2r_filter(data):
print( "applying l2r filter" )
for line in data:
yield line.replace("l", "r")
defaults.preprocessors["text2rl"] = l2r_filter
def x2u_filter(data):
print( "applying x2u filter" )
for line in data:
yield line.replace("x", "u")
defaults.postprocessors["x2text"] = x2u_filter
def u2x_filter(data):
print( "applying u2x filter" )
for line in data:
yield line.replace("u", "x")
defaults.postprocessors["text2x"] = u2x_filter
def test_x2u_filter():
"""Test x2u Filter."""
soll = text.replace("x", "u")
result = "".join([line for line in x2u_filter(textdata)])
print( "soll", repr(text) )
print( "ist", repr(result) )
assert soll == result
Test_x2u_filter= unittest.FunctionTestCase(test_x2u_filter)
TextCodeConverter¶
class Test_TextCodeConverter(unittest.TestCase):
"""Test the TextCodeConverter parent class
"""
def check_marker_regexp_true(self, sample, converter):
match = converter.marker_regexp.search(sample)
print( 'marker: %r; sample %r' %(converter.code_block_marker, sample) )
print( 'match %r'%match )
self.assertIsNotNone( match )
def check_marker_regexp_false(self, sample, converter):
print( 'marker: %r; sample %r' %(converter.code_block_marker, sample) )
self.assertIsNone( converter.marker_regexp.search(sample) is None )
def test_marker_regexp(self):
# Samples
literal = ['::',
' ::',
't ::',
'text::',
' indented::',
' indented ::',
'more text :: ',
' indented text :: ',
'. no-directive::',
'a .. directive:: somewhere::']
directives = ['.. code-block:: python',
' .. code-block:: python',
'.. code-block:: python listings',
' .. code-block:: python listings']
misses = ['.. comment string ::',
'.. ::',
'text:']
# default code_block_marker ('::')
self.converter = TextCodeConverter(textdata)
self.assertEqual( self.converter.code_block_marker, '::' )
# self.converter is not seen by the check_marker_regexp_true() method
for sample in literal:
yield (self.check_marker_regexp_true, sample, self.converter)
for sample in directives+misses:
yield (self.check_marker_regexp_false, sample, self.converter)
# code-block directive as marker
self.converter = TextCodeConverter(textdata,
code_block_marker='.. code-block::')
self.assertEqual( self.converter.code_block_marker, '.. code-block::' )
for sample in directives:
yield (self.check_marker_regexp_true, sample, self.converter)
for sample in literal+misses:
yield (self.check_marker_regexp_false, sample, self.converter)
def test_get_indent(self):
converter = TextCodeConverter(textdata)
self.assertEqual( converter.get_indent("foo"), 0 )
self.assertEqual( converter.get_indent(" foo"), 1 )
self.assertEqual( converter.get_indent(" foo"), 2 )
def test_collect_blocks(self):
converter = TextCodeConverter(textdata)
textblocks = [block for block in collect_blocks(textdata)]
print( textblocks )
self.assertEqual( len(textblocks), 7, "text sample has 7 blocks" )
self.assertEqual( reduce(operator.__add__, textblocks), textdata )
Text2Code¶
class Test_Text2Code(unittest.TestCase):
"""Test the Text2Code class converting rst->code"""
def setUp(self):
self.converter = Text2Code(textdata)
test helper funs
def test_set_state_empty(self):
try:
self.converter.set_state([])
raise AssertionError("should raise StopIteration")
except StopIteration:
pass
def test_set_state_header(self):
"""test for "header" or "documentation" for first block"""
self.converter.state = "" # normally set by the `convert` method
self.converter.set_state([".. header", " block"])
self.assertEqual( self.converter.state, "header" )
self.converter.state = "" # normally set by the `convert` method
self.converter.set_state(["documentation", "block"])
self.assertEqual( self.converter.state, "documentation" )
def test_set_state_code_block(self):
"""test for "header" or "documentation" for "code_block" """
# normally set by the `convert` method
self.converter._textindent = 0
self.converter.state = "code_block"
self.converter.set_state(["documentation", " block"])
self.assertEqual( self.converter.state, "documentation" )
self.converter.state = "code_block"
self.converter.set_state([" documentation", "block"])
self.assertEqual( self.converter.state , "documentation" )
self.converter.state = "code_block"
self.converter.set_state([" code", " block"])
print( self.converter.state )
self.assertEqual( self.converter.state , "code_block" )
def test_header_handler(self):
"""should strip header-string from header"""
self.converter._codeindent = 0
sample = [".. header", " block"]
lines = [line for line in self.converter.header_handler(sample)]
print( lines )
self.assertEqual( lines , ["header", "block"] )
def test_documentation_handler(self):
"""should add comment string to documentation"""
sample = ["doc", "block", ""]
lines = [line for line
in self.converter.documentation_handler(sample)]
print( lines )
self.assertEqual( lines, ["# doc", "# block", "#"] ) # Changed
def test_documentation_handler_set_state(self):
"""should add comment string to documentation"""
sample = ["doc", "block::", ""]
lines = [line for line
in self.converter.documentation_handler(sample)]
print( lines )
self.assertEqual( lines, ["# doc", "# block::", ""] )
self.assertEqual( self.converter.state , "code_block" )
def test_code_block_handler(self):
"""should un-indent code-blocks"""
self.converter._codeindent = 0 # normally set in `convert`
sample = [" code", " block", ""]
lines = [line for line
in self.converter.code_block_handler(sample)]
print( lines )
self.assertEqual( lines , ["code", "block", ""] )
base tests on the “long” test data
def test_call(self):
"""Calling a Text2Code instance should return the converted data as list of lines"""
output = self.converter()
print( repr(codedata) )
print( repr(output) )
self.assertEqual( codedata, output )
def test_call_strip(self):
"""strip=True should strip text parts"""
self.converter.strip = True
output = self.converter()
print( repr(stripped_code.splitlines(True)) )
print( repr(output) )
self.assertEqual( stripped_code.splitlines(True), output )
def test_str(self):
outstr = str(self.converter)
print( repr(code) )
print( repr(outstr) )
self.assertEqual( code, outstr )
def test_str_strip1(self):
"""strip=True should strip text parts.
Version 1 with `strip` given as optional argument"""
outstr = str(Text2Code(textdata, strip=True))
print( "ist ", repr(outstr) )
print( "soll", repr(stripped_code) )
# pprint(outstr)
self.assertEqual( stripped_code, outstr )
def test_str_strip2(self):
"""strip=True should strip text parts
Version 2 with `strip` set after instantiation"""
self.converter.strip = True
outstr = str(self.converter)
print( "ist ", repr(outstr) )
print( "soll", repr(stripped_code) )
# pprint(outstr)
self.assertEqual( stripped_code, outstr )
def test_malindented_code_line(self):
"""raise error if code line is less indented than code-indent"""
data1 = [".. #!/usr/bin/env python3\n", # indent == 4 * " "
"\n",
" print('hello world')"] # indent == 2 * " "
data2 = ["..\t#!/usr/bin/env python3\n", # indent == 8 * " "
"\n",
" print('hello world')"] # indent == 2 * " "
for data in (data1, data2):
try:
blocks = Text2Code(data)()
self.fail( "wrong indent did not raise ValueError" )
except ValueError:
pass
def test_str_different_comment_string(self):
"""Convert only comments with the specified comment string to text
"""
data = [".. #!/usr/bin/env python3\n",
'\n',
'::\n', # leading code block as header
'\n',
" block1 = 'first block'\n",
'\n',
'more text']
soll = "\n".join(["#!/usr/bin/env python3",
"",
"##::",
"",
"block1 = 'first block'",
"",
"##more text"]
)
outstr = str(Text2Code(data, comment_string="##"))
print( "soll:", repr(soll) )
print( "ist: ", repr(outstr) )
self.assertEqual( outstr , soll )
# Filters: test pre- and postprocessing of data
def test_get_filter_preprocessor(self):
"""should return filter from filter_set for language"""
preprocessor = self.converter.get_filter("preprocessors", "rl")
print( preprocessor )
self.assertEqual( preprocessor , l2r_filter )
def test_get_filter_postprocessor(self):
"""should return filter from filter_set for language"""
postprocessor = self.converter.get_filter("postprocessors", "x")
print( postprocessor )
self.assertEqual( postprocessor , u2x_filter )
def test_get_css_postprocessor(self):
"""should return filter from filter_set for language"""
postprocessor = self.converter.get_filter("postprocessors", "css")
print( postprocessor )
self.assertEqual( postprocessor , dumb_c_postprocessor )
def test_get_filter_nonexisting_language_filter(self):
"""should return identity_filter if language has no filter in set"""
preprocessor = self.converter.get_filter("preprocessors", "foo")
print( preprocessor )
self.assertEqual( preprocessor , identity_filter )
def test_get_filter_nonexisting_filter_set(self):
"""should return identity_filter if filter_set does not exist"""
processor = self.converter.get_filter("foo_filters", "foo")
print( processor )
self.assertEqual( processor , identity_filter )
def test_preprocessor(self):
"""Preprocess data with registered preprocessor for language"""
output = Text2Code(textdata, language="x", comment_string="# ")()
soll = [line for line in u2x_filter(codedata)]
print( "soll: ", repr(soll) )
print( "ist: ", repr(output) )
self.assertEqual( output, soll )
def test_postprocessor(self):
"""Preprocess data with registered postprocessor for language"""
output = Text2Code(textdata, language="x", comment_string="# ")()
soll = [line for line in u2x_filter(codedata)]
print( "soll:", repr(soll) )
print( "ist: ", repr(output) )
self.assertEqual( output, soll )
Special Cases¶
Code follows text block without blank line¶
End of text block detected (‘::’) but no paragraph separator (blank line) follows
It is an reStructuredText syntax error, if a “literal block marker” is not followed by a blank line.
Assuming that no double colon at end of line occurs accidentally, pylit could fix this and issue a warning:
# Do we need this feature? (Complicates code a lot)
#
# textsamples["ensure blank line after text"] = (
# """text followed by a literal block::
# block1 = 'first block'
# """,
# """# text followed by a literal block::
#
# block1 = 'first block'
# """)
Text follows code block without blank line¶
End of code block detected (a line not more indented than the preceding text block)
reStructuredText syntax demands a paragraph separator (blank line) before it.
Assuming that the unindent is not accidental, pylit could fix this and issues a warning:
# Do we need this feature? (Complicates code)
# textsamples["ensure blank line after code"] = (
# """::
#
# block1 = 'first block'
# more text
# """,
# """# ::
#
# block1 = 'first block'
#
# more text
# """)
Options follow code-block directive¶
textsamples["code-block directive options"] = (
"""\
::
:option: argument
this = 'code'
""",
"""\
# ::
# :option: argument
this = 'code'
""")
textsamples["no code-block directive options"] = (
"""\
::
text following ``::`` without blank line
more documentation
""",
"""\
# ::
# text following ``::`` without blank line
#
# more documentation
""")
A double colon on a line on its own¶
As a double colon is added by the Code2Text conversion after a text block (if not already present), it could be removed by the Text2Code conversion to keep the source small and pretty.
However, this would put the text and code source line numbers out of sync, which is bad for error reporting, failing doctests, and the JED editor support with the pylit_buffer() function in http://jedmodes.sf.net/mode/pylit.sl .
Maybe this could be left to a post-processing filter:
# textsamples["remove single double colon"] = (
# ["text followed by a literal block\n",
# "\n",
# "::\n",
# "\n",
# " foo = 'first'\n"]
# ["", # empty header
# "# text followed by a literal block\n\n",
# "foo = 'first'\n"]
header samples¶
Convert a leading reStructured text comment (variant: only if there is content on the first line) to a leading code block. Return an empty list, if there is no header.
textsamples["simple header"] = (".. print('hello world')",
"print('hello world')")
textsamples["no header (start with text)"] = (
"""a classical example without header::
print('hello world')
""",
"""# a classical example without header::
print('hello world')
""")
textsamples["no header (start with blank line)"] = (
"""
a classical example without header::
print('hello world')
""",
"""#
# a classical example without header::
print('hello world')
""")
textsamples["standard header, followed by text"] = (
""".. #!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
a classical example with header::
print('hello world')
""",
"""#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
# a classical example with header::
print('hello world')
""")
textsamples["standard header, followed by code"] = (
""".. #!/usr/bin/env python3
print('hello world')
""",
"""#!/usr/bin/env python3
print('hello world')
""")
textsamples["null string"] = ("", "", "")
Code2Text¶
class Test_Code2Text(unittest.TestCase):
def setUp(self):
self.converter = Code2Text(codedata)
## Code2Text.strip_literal_marker
##
## * strip `::`-line as well as preceding blank line if on a line on its own
## * strip `::` if it is preceded by whitespace.
## * convert `::` to a single colon if preceded by text
##
## ::
def check_strip_code_block_marker(self, sample):
"""test Code2Text.strip_code_block_marker"""
ist = sample[0].splitlines(True)
soll = sample[1].splitlines(True)
print( "before", ist )
converter = Code2Text(codedata)
converter.strip_code_block_marker(ist)
print( "soll:", repr(soll) )
print( "ist: ", repr(ist) )
self.assertEqual( ist , soll )
def test_strip_code_block_marker(self):
samples = (("text\n\n::\n\n", "text\n\n"),
("text\n::\n\n", "text\n\n"),
("text ::\n\n", "text\n\n"),
("text::\n\n", "text:\n\n"),
("text:\n\n", "text:\n\n"),
("text\n\n", "text\n\n"),
("text\n", "text\n")
)
for sample in samples:
yield (self.check_strip_code_block_marker, sample)
Code2Text.set_state
def test_set_state(self):
samples = (("code_block", ["code_block\n"], "code_block"),
("code_block", ["#code_block\n"], "code_block"),
("code_block", ["## code_block\n"], "code_block"),
("code_block", ["# documentation\n"], "documentation"),
("code_block", ["# documentation\n"], "documentation"),
("code_block", ["# \n"], "documentation"),
("code_block", ["#\n"], "documentation"),
("code_block", ["\n"], "documentation"),
("", ["code_block\n"], "header"),
("", ["# documentation\n"], "documentation"),
("documentation", ["code_block\n"], "code_block"),
("documentation", ["# documentation\n"], "documentation"),
)
print( "comment string", repr(self.converter.comment_string) )
for (old_state, lines, soll) in samples:
self.converter.state = old_state
self.converter.set_state(lines)
print( repr(lines), "old state", old_state )
print( "soll", repr(soll), end=' ' )
print( "result", repr(self.converter.state) )
self.assertEqual( soll , self.converter.state )
base tests on the “long” test strings
def test_call(self):
output = self.converter()
print( repr(textdata) )
print( repr(output) )
self.assertEqual( textdata, output )
def test_call_strip(self):
output = Code2Text(codedata, strip=True)()
print( repr(stripped_text.splitlines(True)) )
print( repr(output) )
self.assertEqual( stripped_text.splitlines(True) , output )
def test_str(self):
"""Test Code2Text class converting code->text"""
outstr = str(self.converter)
# print( text )
print( "soll:", repr(text) )
print( "ist: ", repr(outstr) )
self.assertEqual( text, outstr )
def test_str_strip(self):
"""Test Code2Text class converting code->rst with strip=True
Should strip code blocks
"""
outstr = str(Code2Text(codedata, strip=True))
print( repr(stripped_text) )
print( repr(outstr) )
self.assertEqual( stripped_text , outstr )
def test_str_different_comment_string(self):
"""Convert only comments with the specified comment string to text
"""
outstr = str(Code2Text(codedata, comment_string="##", strip=True))
print( outstr )
self.assertEqual( outstr , "" )
data = ["# ::\n",
"\n",
"block1 = 'first block'\n",
"\n",
"## more text"]
soll = "\n".join(['.. # ::', # leading code block as header
'', # Changed
" block1 = 'first block'",
'', # Changed
' more text'] # keep space (not part of comment string)
)
outstr = str(Code2Text(data, comment_string="##"))
print( "soll:", repr(soll) )
print( "ist: ", repr(outstr) )
self.assertEqual( outstr, soll )
def test_call_different_code_block_marker(self):
"""recognize specified code-block marker
"""
data = ["# .. code-block:: python\n",
"\n",
"block1 = 'first block'\n",
"\n",
"# more text\n"]
soll = ['.. code-block:: python\n',
'\n',
" block1 = 'first block'\n",
'\n', # Changed
' more text\n'] # keep space (not part of comment string)
converter = Code2Text(data, code_block_marker='.. code-block::')
output = converter()
print( "soll:", repr(soll) )
print( "ist: ", repr(output) )
self.assertEqual( output, soll )
# Filters: test pre- and postprocessing of Code2Text data conversion
def test_get_filter_preprocessor(self):
"""should return Code2Text preprocessor for language"""
preprocessor = self.converter.get_filter("preprocessors", "rl")
print( preprocessor )
self.assertEqual( preprocessor , r2l_filter )
def test_get_css_preprocessor(self):
"""should return filter from filter_set for language"""
preprocessor = self.converter.get_filter("preprocessors", "css")
print( preprocessor )
self.assertEqual( preprocessor , dumb_c_preprocessor )
def test_get_filter_postprocessor(self):
"""should return Code2Text postprocessor for language"""
postprocessor = self.converter.get_filter("postprocessors", "x")
print( postprocessor )
self.assertEqual( postprocessor , x2u_filter )
def test_get_filter_nonexisting_language_filter(self):
"""should return identity_filter if language has no filter in set"""
preprocessor = self.converter.get_filter("preprocessors", "foo")
print( preprocessor )
self.assertEqual( preprocessor , identity_filter )
def test_get_filter_nonexisting_filter_set(self):
"""should return identity_filter if filter_set does not exist"""
processor = self.converter.get_filter("foo_filters", "foo")
print( processor )
self.assertEqual( processor , identity_filter )
def test_preprocessor(self):
"""Preprocess data with registered preprocessor for language"""
converter = Code2Text(codedata, language="rl", comment_string="# ")
print( "preprocessor", converter.preprocessor )
print( "postprocessor", converter.postprocessor )
output = converter()
soll = [line.replace("r", "l") for line in textdata]
print( "ist: ", repr(output) )
print( "soll:", repr(soll) )
self.assertEqual( output, soll )
def test_postprocessor(self):
"""Postprocess data with registered postprocessor for language"""
output = Code2Text(codedata, language="x", comment_string="# ")()
soll = [line.replace("x", "u") for line in textdata]
print( "soll:", repr(soll) )
print( "ist: ", repr(output) )
self.assertEqual( output, soll )
Special cases¶
blank comment line¶
Normally, whitespace in the comment string is significant, i.e. with
comment_string = "# "
, a line "#something\n"
will count as code.
However, if a comment line is blank, trailing whitespace in the comment
string should be ignored, i.e. #\n
is recognised as a blank text line:
codesamples["ignore trailing whitespace in comment string for blank line"] = (
"""# ::
block1 = 'first block'
#
# more text
""",
"""::
block1 = 'first block'
more text
""")
No blank line after text¶
If a matching comment precedes or follows a code line (i.e. any line without matching comment) without a blank line in between, it counts as code line.
This will keep small inline comments close to the code they comment on. It will also keep blocks together where one commented line does not match the comment string (the whole block will be kept as commented code)
codesamples["comment before code (without blank line)"] = (
"""\
# this is text::
# this is a comment
foo = 'first'
""",
"""\
this is text::
# this is a comment
foo = 'first'
""",
"""\
this is text:
""")
codesamples["comment block before code (without blank line)"] = (
"""\
# no text (watch the comment sign in the next line)::
#
# this is a comment
foo = 'first'
""",
"""\
.. # no text (watch the comment sign in the next line)::
#
# this is a comment
foo = 'first'
""",
"")
codesamples["comment after code (without blank line)"] = (
"""\
# ::
block1 = 'first block'
# commented code
# text again
""",
"""\
::
block1 = 'first block'
# commented code
text again
""",
"""
text again
""")
codesamples["comment block after code (without blank line)"] = (
"""\
# ::
block1 = 'first block'
# commented code
#
# still comment
""",
"""::
block1 = 'first block'
# commented code
#
# still comment
""",
"""
""")
missing literal block marker¶
If text (with matching comment string) is followed by code (line(s) without matching comment string), but there is no double colon at the end, back conversion would not recognise the end of text!
Therefore, pylit adds a paragraph containing only ::
– the literal
block marker in expanded form. (While it would in many cases be nicer to
add the double colon to the last text line, this is not always valid rst
syntax, e.g. after a section header or a list. Therefore the automatic
insertion will use the save form, feel free to correct this by hand.):
codesamples["insert missing double colon after text block"] = (
"""# text followed by code without double colon
foo = 'first'
""",
"""text followed by code without double colon
::
foo = 'first'
""",
"""text followed by code without double colon
""")
codesamples["ignore directive options when looking for code-block marker"] = (
"""\
# ::
# :option: argument
# :option2: argument
this = 'code'
""",
"""\
::
:option: argument
:option2: argument
this = 'code'
""")
codesamples["code-block marker followed by text not a directive option"] = (
"""\
# ::
# text following ``::`` without blank line
this = 'code'
""",
"""\
::
text following ``::`` without blank line
::
this = 'code'
""")
header samples¶
Convert a header (leading code block) to a reStructured text comment.
codesamples["no matching comment, just code"] = (
"""print('hello world')
print('ende')
""",
""".. print('hello world')
print('ende')
""")
codesamples["empty header (start with matching comment)"] = (
"""# a classical example without header::
print('hello world')
""",
"""a classical example without header::
print('hello world')
""",
"""a classical example without header:
""")
codesamples["standard header, followed by text"] = (
"""#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
# a classical example with header::
print('hello world')
""",
""".. #!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-
a classical example with header::
print('hello world')
""",
"""a classical example with header:
""")
codesamples["standard header, followed by code"] = (
"""#!/usr/bin/env python3
print('hello world')
""",
""".. #!/usr/bin/env python3
print('hello world')
""",
"")
Filter tests¶
css_code = ['/* import the default Docutils style sheet */\n',
'/* --------------------------------------- */\n',
'\n',
'/* :: */\n',
'\n',
'/*comment*/\n',
'@import url("html4css1.css"); /* style */\n']
css_filtered_code = ['// import the default Docutils style sheet\n',
'// ---------------------------------------\n',
'\n',
'// ::\n',
'\n',
'/*comment*/\n',
'@import url("html4css1.css"); /* style */\n']
def test_dumb_c_preprocessor():
"""convert `C` to `C++` comments"""
output = [line for line in dumb_c_preprocessor(css_code)]
print("ist: {0!r}".format(output))
print("soll: {0!r}".format(css_filtered_code))
assert output == css_filtered_code
Test_dumb_c_preprocessor= unittest.FunctionTestCase(test_dumb_c_preprocessor)
def test_dumb_c_postprocessor():
"""convert `C++` to `C` comments"""
output = [line for line in dumb_c_postprocessor(css_filtered_code)]
print("ist: {0!r}".format(output))
print("soll: {0!r}".format(css_code))
assert output == css_code
Test_dumb_c_postprocessor= unittest.FunctionTestCase(test_dumb_c_postprocessor)
The Main Program¶
The overall main parts of the test program.
We need to build the suite from the various tests.
We can’t trust to default test discovery because of the two functions
which build test instances. make_Text2Code_samples()
and
make_Code2Text_samples()
are not simple tests; they create
tests from source code dictionaries.
We’ll use the unittest.TextTestRunner
to execute the tests.
def suite():
s = unittest.TestSuite(
[
make_Text2Code_samples(), make_Code2Text_samples(),
Test_x2u_filter,
Test_dumb_c_preprocessor, Test_dumb_c_postprocessor,
unittest.defaultTestLoader.loadTestsFromTestCase(Test_DefaultDict),
unittest.defaultTestLoader.loadTestsFromTestCase(Test_TextCodeConverter),
unittest.defaultTestLoader.loadTestsFromTestCase(Test_Text2Code),
unittest.defaultTestLoader.loadTestsFromTestCase(Test_Code2Text),
]
)
return s
if __name__ == "__main__":
s= suite()
unittest.TextTestRunner(verbosity=2).run(s)
Documentation Notes¶
This requires a different comment string. The shell command looks like this.
python3 pylit.py --comment-string='## ' test/pylit_test.py docs/examples/pylit_test.py.txt
The use of ## comments makes it possible to handle “::” in the code.