Fixes in linepsacing doc.
[docutils.git] / test / DocutilsTestSupport.py
blob5c5c49d42b3488e23b15765bbc5b5a62a83d9f7b
1 # Authors: David Goodger; Garth Kidd
2 # Contact: goodger@users.sourceforge.net
3 # Revision: $Revision$
4 # Date: $Date$
5 # Copyright: This module has been placed in the public domain.
7 """
8 Exports the following:
10 :Modules:
11 - `statemachine` is 'docutils.statemachine'
12 - `nodes` is 'docutils.nodes'
13 - `urischemes` is 'docutils.urischemes'
14 - `utils` is 'docutils.utils'
15 - `transforms` is 'docutils.transforms'
16 - `states` is 'docutils.parsers.rst.states'
17 - `tableparser` is 'docutils.parsers.rst.tableparser'
19 :Classes:
20 - `CustomTestSuite`
21 - `CustomTestCase`
22 - `TransformTestSuite`
23 - `TransformTestCase`
24 - `ParserTestSuite`
25 - `ParserTestCase`
26 - `PEPParserTestSuite`
27 - `PEPParserTestCase`
28 - `GridTableParserTestSuite`
29 - `GridTableParserTestCase`
30 - `SimpleTableParserTestSuite`
31 - `SimpleTableParserTestCase`
32 - `WriterPublishTestCase`
33 - `LatexWriterPublishTestCase`
34 - `PseudoXMLWriterPublishTestCase`
35 - `HtmlWriterPublishTestCase`
36 - `PublishTestSuite`
37 - `HtmlFragmentTestSuite`
38 - `DevNull` (output sink)
39 """
40 __docformat__ = 'reStructuredText'
42 import sys
43 import os
44 import unittest
45 import difflib
46 import inspect
47 from pprint import pformat
48 from types import UnicodeType
49 import package_unittest
50 import docutils
51 import docutils.core
52 from docutils import frontend, nodes, statemachine, urischemes, utils
53 from docutils.transforms import universal
54 from docutils.parsers import rst
55 from docutils.parsers.rst import states, tableparser, roles, languages
56 from docutils.readers import standalone, pep
57 from docutils.statemachine import StringList, string2lines
59 if sys.hexversion >= 0x02020000: # Python 2.2
60 from docutils.readers.python import moduleparser
61 else:
62 moduleparser = None
64 try:
65 import mypdb as pdb
66 except:
67 import pdb
70 # Hack to make repr(StringList) look like repr(list):
71 StringList.__repr__ = StringList.__str__
74 class DevNull:
76 """Output sink."""
78 def write(self, string):
79 pass
82 class CustomTestSuite(unittest.TestSuite):
84 """
85 A collection of custom TestCases.
87 """
89 id = ''
90 """Identifier for the TestSuite. Prepended to the
91 TestCase identifiers to make identification easier."""
93 next_test_case_id = 0
94 """The next identifier to use for non-identified test cases."""
96 def __init__(self, tests=(), id=None):
97 """
98 Initialize the CustomTestSuite.
100 Arguments:
102 id -- identifier for the suite, prepended to test cases.
104 unittest.TestSuite.__init__(self, tests)
105 if id is None:
106 mypath = os.path.abspath(
107 sys.modules[CustomTestSuite.__module__].__file__)
108 outerframes = inspect.getouterframes(inspect.currentframe())
109 for outerframe in outerframes[1:]:
110 if outerframe[3] != '__init__':
111 callerpath = outerframe[1]
112 if callerpath is None:
113 # It happens sometimes. Why is a mystery.
114 callerpath = os.getcwd()
115 callerpath = os.path.abspath(callerpath)
116 break
117 mydir, myname = os.path.split(mypath)
118 if not mydir:
119 mydir = os.curdir
120 if callerpath.startswith(mydir):
121 self.id = callerpath[len(mydir) + 1:] # caller's module
122 else:
123 self.id = callerpath
124 else:
125 self.id = id
127 def addTestCase(self, test_case_class, method_name, input, expected,
128 id=None, run_in_debugger=0, short_description=None,
129 **kwargs):
131 Create a custom TestCase in the CustomTestSuite.
132 Also return it, just in case.
134 Arguments:
136 test_case_class --
137 method_name --
138 input -- input to the parser.
139 expected -- expected output from the parser.
140 id -- unique test identifier, used by the test framework.
141 run_in_debugger -- if true, run this test under the pdb debugger.
142 short_description -- override to default test description.
144 if id is None: # generate id if required
145 id = self.next_test_case_id
146 self.next_test_case_id += 1
147 # test identifier will become suiteid.testid
148 tcid = '%s: %s' % (self.id, id)
149 # generate and add test case
150 tc = test_case_class(method_name, input, expected, tcid,
151 run_in_debugger=run_in_debugger,
152 short_description=short_description,
153 **kwargs)
154 self.addTest(tc)
155 return tc
157 def generate_no_tests(self, *args, **kwargs):
158 pass
161 class CustomTestCase(unittest.TestCase):
163 compare = difflib.Differ().compare
164 """Comparison method shared by all subclasses."""
166 def __init__(self, method_name, input, expected, id,
167 run_in_debugger=0, short_description=None):
169 Initialise the CustomTestCase.
171 Arguments:
173 method_name -- name of test method to run.
174 input -- input to the parser.
175 expected -- expected output from the parser.
176 id -- unique test identifier, used by the test framework.
177 run_in_debugger -- if true, run this test under the pdb debugger.
178 short_description -- override to default test description.
180 self.id = id
181 self.input = input
182 self.expected = expected
183 self.run_in_debugger = run_in_debugger
184 # Ring your mother.
185 unittest.TestCase.__init__(self, method_name)
187 def __str__(self):
189 Return string conversion. Overridden to give test id, in addition to
190 method name.
192 return '%s; %s' % (self.id, unittest.TestCase.__str__(self))
194 def __repr__(self):
195 return "<%s %s>" % (self.id, unittest.TestCase.__repr__(self))
197 def compare_output(self, input, output, expected):
198 """`input`, `output`, and `expected` should all be strings."""
199 if type(input) == UnicodeType:
200 input = input.encode('raw_unicode_escape')
201 if type(output) == UnicodeType:
202 output = output.encode('raw_unicode_escape')
203 if type(expected) == UnicodeType:
204 expected = expected.encode('raw_unicode_escape')
205 try:
206 self.assertEquals('\n' + output, '\n' + expected)
207 except AssertionError:
208 print >>sys.stderr, '\n%s\ninput:' % (self,)
209 print >>sys.stderr, input
210 print >>sys.stderr, '-: expected\n+: output'
211 print >>sys.stderr, ''.join(self.compare(expected.splitlines(1),
212 output.splitlines(1)))
213 raise
215 def skip_test(self):
216 print >>sys.stderr, '%s: Test skipped' % self
219 class TransformTestSuite(CustomTestSuite):
222 A collection of TransformTestCases.
224 A TransformTestSuite instance manufactures TransformTestCases,
225 keeps track of them, and provides a shared test fixture (a-la
226 setUp and tearDown).
229 def __init__(self, parser):
230 self.parser = parser
231 """Parser shared by all test cases."""
233 CustomTestSuite.__init__(self)
235 def generateTests(self, dict, dictname='totest',
236 testmethod='test_transforms'):
238 Stock the suite with test cases generated from a test data dictionary.
240 Each dictionary key (test type's name) maps to a list of transform
241 classes and list of tests. Each test is a list: input, expected
242 output, optional modifier. The optional third entry, a behavior
243 modifier, can be 0 (temporarily disable this test) or 1 (run this test
244 under the pdb debugger). Tests should be self-documenting and not
245 require external comments.
247 for name, (transforms, cases) in dict.items():
248 for casenum in range(len(cases)):
249 case = cases[casenum]
250 run_in_debugger = 0
251 if len(case)==3:
252 if case[2]:
253 run_in_debugger = 1
254 else:
255 continue
256 self.addTestCase(
257 TransformTestCase, testmethod,
258 transforms=transforms, parser=self.parser,
259 input=case[0], expected=case[1],
260 id='%s[%r][%s]' % (dictname, name, casenum),
261 run_in_debugger=run_in_debugger)
264 class TransformTestCase(CustomTestCase):
267 Output checker for the transform.
269 Should probably be called TransformOutputChecker, but I can deal with
270 that later when/if someone comes up with a category of transform test
271 cases that have nothing to do with the input and output of the transform.
274 option_parser = frontend.OptionParser(components=(rst.Parser,))
275 settings = option_parser.get_default_values()
276 settings.report_level = 1
277 settings.halt_level = 5
278 settings.debug = package_unittest.debug
279 settings.warning_stream = DevNull()
280 unknown_reference_resolvers = ()
282 def __init__(self, *args, **kwargs):
283 self.transforms = kwargs['transforms']
284 """List of transforms to perform for this test case."""
286 self.parser = kwargs['parser']
287 """Input parser for this test case."""
289 del kwargs['transforms'], kwargs['parser'] # only wanted here
290 CustomTestCase.__init__(self, *args, **kwargs)
292 def supports(self, format):
293 return 1
295 def test_transforms(self):
296 if self.run_in_debugger:
297 pdb.set_trace()
298 document = utils.new_document('test data', self.settings)
299 self.parser.parse(self.input, document)
300 # Don't do a ``populate_from_components()`` because that would
301 # enable the Transformer's default transforms.
302 document.transformer.add_transforms(self.transforms)
303 document.transformer.add_transform(universal.TestMessages)
304 document.transformer.components['writer'] = self
305 document.transformer.apply_transforms()
306 output = document.pformat()
307 self.compare_output(self.input, output, self.expected)
309 def test_transforms_verbosely(self):
310 if self.run_in_debugger:
311 pdb.set_trace()
312 print '\n', self.id
313 print '-' * 70
314 print self.input
315 document = utils.new_document('test data', self.settings)
316 self.parser.parse(self.input, document)
317 print '-' * 70
318 print document.pformat()
319 for transformClass in self.transforms:
320 transformClass(document).apply()
321 output = document.pformat()
322 print '-' * 70
323 print output
324 self.compare_output(self.input, output, self.expected)
327 class ParserTestCase(CustomTestCase):
330 Output checker for the parser.
332 Should probably be called ParserOutputChecker, but I can deal with
333 that later when/if someone comes up with a category of parser test
334 cases that have nothing to do with the input and output of the parser.
337 parser = rst.Parser()
338 """Parser shared by all ParserTestCases."""
340 option_parser = frontend.OptionParser(components=(rst.Parser,))
341 settings = option_parser.get_default_values()
342 settings.report_level = 5
343 settings.halt_level = 5
344 settings.debug = package_unittest.debug
346 def test_parser(self):
347 if self.run_in_debugger:
348 pdb.set_trace()
349 document = utils.new_document('test data', self.settings)
350 # Remove any additions made by "role" directives:
351 roles._roles = {}
352 self.parser.parse(self.input, document)
353 output = document.pformat()
354 self.compare_output(self.input, output, self.expected)
357 class ParserTestSuite(CustomTestSuite):
360 A collection of ParserTestCases.
362 A ParserTestSuite instance manufactures ParserTestCases,
363 keeps track of them, and provides a shared test fixture (a-la
364 setUp and tearDown).
367 test_case_class = ParserTestCase
369 def generateTests(self, dict, dictname='totest'):
371 Stock the suite with test cases generated from a test data dictionary.
373 Each dictionary key (test type name) maps to a list of tests. Each
374 test is a list: input, expected output, optional modifier. The
375 optional third entry, a behavior modifier, can be 0 (temporarily
376 disable this test) or 1 (run this test under the pdb debugger). Tests
377 should be self-documenting and not require external comments.
379 for name, cases in dict.items():
380 for casenum in range(len(cases)):
381 case = cases[casenum]
382 run_in_debugger = 0
383 if len(case)==3:
384 if case[2]:
385 run_in_debugger = 1
386 else:
387 continue
388 self.addTestCase(
389 self.test_case_class, 'test_parser',
390 input=case[0], expected=case[1],
391 id='%s[%r][%s]' % (dictname, name, casenum),
392 run_in_debugger=run_in_debugger)
395 class PEPParserTestCase(ParserTestCase):
397 """PEP-specific parser test case."""
399 parser = rst.Parser(rfc2822=1, inliner=pep.Inliner())
400 """Parser shared by all PEPParserTestCases."""
402 option_parser = frontend.OptionParser(components=(rst.Parser, pep.Reader))
403 settings = option_parser.get_default_values()
404 settings.report_level = 5
405 settings.halt_level = 5
406 settings.debug = package_unittest.debug
409 class PEPParserTestSuite(ParserTestSuite):
411 """A collection of PEPParserTestCases."""
413 test_case_class = PEPParserTestCase
416 class GridTableParserTestCase(CustomTestCase):
418 parser = tableparser.GridTableParser()
420 def test_parse_table(self):
421 self.parser.setup(StringList(string2lines(self.input), 'test data'))
422 try:
423 self.parser.find_head_body_sep()
424 self.parser.parse_table()
425 output = self.parser.cells
426 except Exception, details:
427 output = '%s: %s' % (details.__class__.__name__, details)
428 self.compare_output(self.input, pformat(output) + '\n',
429 pformat(self.expected) + '\n')
431 def test_parse(self):
432 try:
433 output = self.parser.parse(StringList(string2lines(self.input),
434 'test data'))
435 except Exception, details:
436 output = '%s: %s' % (details.__class__.__name__, details)
437 self.compare_output(self.input, pformat(output) + '\n',
438 pformat(self.expected) + '\n')
441 class GridTableParserTestSuite(CustomTestSuite):
444 A collection of GridTableParserTestCases.
446 A GridTableParserTestSuite instance manufactures GridTableParserTestCases,
447 keeps track of them, and provides a shared test fixture (a-la setUp and
448 tearDown).
451 test_case_class = GridTableParserTestCase
453 def generateTests(self, dict, dictname='totest'):
455 Stock the suite with test cases generated from a test data dictionary.
457 Each dictionary key (test type name) maps to a list of tests. Each
458 test is a list: an input table, expected output from parse_table(),
459 expected output from parse(), optional modifier. The optional fourth
460 entry, a behavior modifier, can be 0 (temporarily disable this test)
461 or 1 (run this test under the pdb debugger). Tests should be
462 self-documenting and not require external comments.
464 for name, cases in dict.items():
465 for casenum in range(len(cases)):
466 case = cases[casenum]
467 run_in_debugger = 0
468 if len(case) == 4:
469 if case[-1]:
470 run_in_debugger = 1
471 else:
472 continue
473 self.addTestCase(self.test_case_class, 'test_parse_table',
474 input=case[0], expected=case[1],
475 id='%s[%r][%s]' % (dictname, name, casenum),
476 run_in_debugger=run_in_debugger)
477 self.addTestCase(self.test_case_class, 'test_parse',
478 input=case[0], expected=case[2],
479 id='%s[%r][%s]' % (dictname, name, casenum),
480 run_in_debugger=run_in_debugger)
483 class SimpleTableParserTestCase(GridTableParserTestCase):
485 parser = tableparser.SimpleTableParser()
488 class SimpleTableParserTestSuite(CustomTestSuite):
491 A collection of SimpleTableParserTestCases.
494 test_case_class = SimpleTableParserTestCase
496 def generateTests(self, dict, dictname='totest'):
498 Stock the suite with test cases generated from a test data dictionary.
500 Each dictionary key (test type name) maps to a list of tests. Each
501 test is a list: an input table, expected output from parse(), optional
502 modifier. The optional third entry, a behavior modifier, can be 0
503 (temporarily disable this test) or 1 (run this test under the pdb
504 debugger). Tests should be self-documenting and not require external
505 comments.
507 for name, cases in dict.items():
508 for casenum in range(len(cases)):
509 case = cases[casenum]
510 run_in_debugger = 0
511 if len(case) == 3:
512 if case[-1]:
513 run_in_debugger = 1
514 else:
515 continue
516 self.addTestCase(self.test_case_class, 'test_parse',
517 input=case[0], expected=case[1],
518 id='%s[%r][%s]' % (dictname, name, casenum),
519 run_in_debugger=run_in_debugger)
522 class PythonModuleParserTestCase(CustomTestCase):
524 def test_parser(self):
525 if self.run_in_debugger:
526 pdb.set_trace()
527 module = moduleparser.parse_module(self.input, 'test data').pformat()
528 output = str(module)
529 self.compare_output(self.input, output, self.expected)
531 def test_token_parser_rhs(self):
532 if self.run_in_debugger:
533 pdb.set_trace()
534 tr = moduleparser.TokenParser(self.input)
535 output = tr.rhs(1)
536 self.compare_output(self.input, output, self.expected)
539 class PythonModuleParserTestSuite(CustomTestSuite):
542 A collection of PythonModuleParserTestCase.
545 notified = None
547 def __init__(self, *args, **kwargs):
548 if moduleparser is None:
549 if not self.notified:
550 print ('Tests of docutils.readers.python skipped; '
551 'Python 2.2 or higher required.')
552 PythonModuleParserTestSuite.notified = 1
553 self.generateTests = self.generate_no_tests
554 CustomTestSuite.__init__(self, *args, **kwargs)
556 def generateTests(self, dict, dictname='totest',
557 testmethod='test_parser'):
559 Stock the suite with test cases generated from a test data dictionary.
561 Each dictionary key (test type's name) maps to a list of tests. Each
562 test is a list: input, expected output, optional modifier. The
563 optional third entry, a behavior modifier, can be 0 (temporarily
564 disable this test) or 1 (run this test under the pdb debugger). Tests
565 should be self-documenting and not require external comments.
567 for name, cases in dict.items():
568 for casenum in range(len(cases)):
569 case = cases[casenum]
570 run_in_debugger = 0
571 if len(case)==3:
572 if case[2]:
573 run_in_debugger = 1
574 else:
575 continue
576 self.addTestCase(
577 PythonModuleParserTestCase, testmethod,
578 input=case[0], expected=case[1],
579 id='%s[%r][%s]' % (dictname, name, casenum),
580 run_in_debugger=run_in_debugger)
583 class WriterPublishTestCase(CustomTestCase, docutils.SettingsSpec):
586 Test case for publish.
589 settings_default_overrides = {'_disable_config': 1}
590 writer_name = '' # override in subclasses
592 def test_publish(self):
593 if self.run_in_debugger:
594 pdb.set_trace()
595 output = docutils.core.publish_string(
596 source=self.input,
597 reader_name='standalone',
598 parser_name='restructuredtext',
599 writer_name=self.writer_name,
600 settings_spec=self)
601 self.compare_output(self.input, output, self.expected)
604 class LatexWriterPublishTestCase(WriterPublishTestCase):
607 Test case for Latex writer.
610 writer_name = 'latex'
613 class PseudoXMLWriterPublishTestCase(WriterPublishTestCase):
616 Test case for pseudo-XML writer.
619 writer_name = 'pseudoxml'
622 class PublishTestSuite(CustomTestSuite):
624 TEST_CLASSES = {
625 'latex': LatexWriterPublishTestCase,
626 'pseudoxml': PseudoXMLWriterPublishTestCase,
629 def __init__(self, writer_name):
631 `writer_name` is the name of the writer
632 to use. It must be a key in `TEST_CLASSES`.
634 CustomTestSuite.__init__(self)
635 self.test_class = self.TEST_CLASSES[writer_name]
637 def generateTests(self, dict, dictname='totest'):
638 for name, cases in dict.items():
639 for casenum in range(len(cases)):
640 case = cases[casenum]
641 run_in_debugger = 0
642 if len(case)==3:
643 if case[2]:
644 run_in_debugger = 1
645 else:
646 continue
647 self.addTestCase(
648 self.test_class, 'test_publish',
649 input=case[0], expected=case[1],
650 id='%s[%r][%s]' % (dictname, name, casenum),
651 run_in_debugger=run_in_debugger)
654 class HtmlPublishPartsTestSuite(CustomTestSuite):
656 def generateTests(self, dict, dictname='totest'):
657 for name, (settings_overrides, cases) in dict.items():
658 for casenum in range(len(cases)):
659 case = cases[casenum]
660 run_in_debugger = 0
661 if len(case)==3:
662 if case[2]:
663 run_in_debugger = 1
664 else:
665 continue
666 self.addTestCase(
667 HtmlWriterPublishPartsTestCase, 'test_publish',
668 settings_overrides=settings_overrides,
669 input=case[0], expected=case[1],
670 id='%s[%r][%s]' % (dictname, name, casenum),
671 run_in_debugger=run_in_debugger)
674 class HtmlWriterPublishPartsTestCase(WriterPublishTestCase):
677 Test case for HTML writer via the publish_parts interface.
680 writer_name = 'html'
682 def __init__(self, *args, **kwargs):
683 self.settings_overrides = kwargs['settings_overrides']
684 """Settings overrides to use for this test case."""
686 del kwargs['settings_overrides'] # only wanted here
687 CustomTestCase.__init__(self, *args, **kwargs)
689 def test_publish(self):
690 if self.run_in_debugger:
691 pdb.set_trace()
692 parts = docutils.core.publish_parts(
693 source=self.input,
694 reader_name='standalone',
695 parser_name='restructuredtext',
696 writer_name=self.writer_name,
697 settings_spec=self,
698 settings_overrides=self.settings_overrides)
699 output = self.format_output(parts)
700 # interpolate standard variables:
701 expected = self.expected % {'version': docutils.__version__}
702 self.compare_output(self.input, output, expected)
704 standard_meta_value = """\
705 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
706 <meta name="generator" content="Docutils %s: http://docutils.sourceforge.net/" />
707 """ % docutils.__version__
708 standard_stylesheet_value = ('<link rel="stylesheet" href="default.css" '
709 'type="text/css" />\n')
711 def format_output(self, parts):
712 """Minimize & standardize the output."""
713 # remove redundant bits:
714 del parts['whole']
715 del parts['body']
716 # remove standard bits:
717 parts['meta'] = parts['meta'].replace(self.standard_meta_value, '')
718 if parts['stylesheet'] == self.standard_stylesheet_value:
719 del parts['stylesheet']
720 # remove empty values:
721 for key in parts.keys():
722 if not parts[key]:
723 del parts[key]
724 # standard output format:
725 keys = parts.keys()
726 keys.sort()
727 output = []
728 for key in keys:
729 output.append("%r: '''%s'''"
730 % (key, parts[key].encode('raw_unicode_escape')))
731 if output[-1].endswith("\n'''"):
732 output[-1] = output[-1][:-4] + "\\n'''"
733 return '{' + ',\n '.join(output) + '}\n'
736 def exception_data(code):
738 Execute `code` and return the resulting exception, the exception arguments,
739 and the formatted exception string.
741 try:
742 exec(code)
743 except Exception, detail:
744 return (detail, detail.args,
745 '%s: %s' % (detail.__class__.__name__, detail))