tox does not test with unsupported python versions.
[docutils.git] / docutils / test / test_publisher.py
blobbf13cc9d3c2a5e597fdd1ee1dd35851b35a533b2
1 #!/usr/bin/env python3
3 # $Id$
4 # Author: Martin Blais <blais@furius.ca>
5 # Copyright: This module has been placed in the public domain.
7 """
8 Test the `Publisher` facade and the ``publish_*`` convenience functions.
9 """
10 import os.path
11 import pickle
12 from pathlib import Path
13 import sys
14 import unittest
16 if __name__ == '__main__':
17 # prepend the "docutils root" to the Python library path
18 # so we import the local `docutils` package.
19 sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
21 import docutils
22 from docutils import core, nodes
24 # DATA_ROOT is ./test/data/ from the docutils root
25 DATA_ROOT = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data')
27 test_document = """\
28 Test Document
29 =============
31 This is a test document with a broken reference: nonexistent_
32 """
33 pseudoxml_output = """\
34 <document ids="test-document" names="test\\ document" source="<string>" title="Test Document">
35 <title>
36 Test Document
37 <paragraph>
38 This is a test document with a broken reference: \n\
39 <problematic ids="problematic-1" refid="system-message-1">
40 nonexistent_
41 <section classes="system-messages">
42 <title>
43 Docutils System Messages
44 <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="4" source="<string>" type="ERROR">
45 <paragraph>
46 Unknown target name: "nonexistent".
47 """
48 exposed_pseudoxml_output = """\
49 <document ids="test-document" internal:refnames="{'nonexistent': [<reference: <#text: 'nonexistent'>>]}" names="test\\ document" source="<string>" title="Test Document">
50 <title>
51 Test Document
52 <paragraph>
53 This is a test document with a broken reference: \n\
54 <problematic ids="problematic-1" refid="system-message-1">
55 nonexistent_
56 <section classes="system-messages">
57 <title>
58 Docutils System Messages
59 <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="4" source="<string>" type="ERROR">
60 <paragraph>
61 Unknown target name: "nonexistent".
62 """
65 class PublisherTests(unittest.TestCase):
67 settings = {'_disable_config': True,
68 'datestamp': False}
70 def test_input_error_handling(self):
71 # core.publish_cmdline(argv=['nonexisting/path'])
72 # exits with a short message, if `traceback` is False,
74 # pass IOErrors to calling application if `traceback` is True
75 with self.assertRaises(IOError):
76 core.publish_cmdline(argv=['nonexisting/path'],
77 settings_overrides={'traceback': True})
79 def test_output_error_handling(self):
80 # pass IOErrors to calling application if `traceback` is True
81 with self.assertRaises(docutils.io.OutputError):
82 core.publish_cmdline(argv=[os.path.join(DATA_ROOT, 'include.txt'),
83 'nonexisting/path'],
84 settings_overrides={'traceback': True})
86 def test_set_destination(self):
87 # Exit if `_destination` and `output` settings conflict.
88 publisher = core.Publisher()
89 publisher.get_settings(output='out_name', _destination='out_name')
90 # no conflict if both have same value:
91 publisher.set_destination()
92 # no conflict if both are overridden:
93 publisher.set_destination(destination_path='winning_dest')
94 # ... also sets _destination to 'winning_dest' -> conflict
95 with self.assertRaises(SystemExit):
96 publisher.set_destination()
98 def test_destination_output_conflict(self):
99 # Exit if positional argument and --output option conflict.
100 settings = {'output': 'out_name'}
101 with self.assertRaises(SystemExit):
102 core.publish_cmdline(argv=['-', 'dest_name'],
103 settings_overrides=settings)
105 def test_publish_string_input_encoding(self):
106 """Test handling of encoded input."""
107 # Transparently decode `bytes` source (with "input_encoding" setting)
108 # default: auto-detect, fallback utf-8
109 # Output is encoded according to "output_encoding" setting.
110 settings = dict(self.settings)
111 source = 'test → me'
112 expected = ('<document source="<string>">\n'
113 ' <paragraph>\n'
114 ' test → me\n').encode('utf-8')
115 output = core.publish_string(source.encode('utf-16'),
116 settings_overrides=settings)
117 self.assertEqual(expected, output)
119 # encoding declaration in source
120 source = '.. encoding: latin1\n\nGrüße'
121 # don't encode output (return `str`)
122 settings['output_encoding'] = 'unicode'
123 output = core.publish_string(source.encode('utf-16'),
124 settings_overrides=settings)
125 self.assertTrue(output.endswith('Grüße\n'))
127 def test_publish_string_output_encoding(self):
128 settings = dict(self.settings)
129 settings['output_encoding'] = 'latin1'
130 settings['output_encoding_error_handler'] = 'replace'
131 source = 'Grüß → dich'
132 expected = ('<document source="<string>">\n'
133 ' <paragraph>\n'
134 ' Grüß → dich\n')
135 # encode output, return `bytes`
136 output = bytes(core.publish_string(source,
137 settings_overrides=settings))
138 self.assertEqual(expected.encode('latin1', 'replace'), output)
140 def test_publish_string_output_encoding_odt(self):
141 """The ODT writer generates a zip archive, not a `str`.
143 TODO: return `str` with document as "flat XML" (.fodt).
145 settings = dict(self.settings)
146 settings['output_encoding'] = 'unicode'
147 with self.assertRaises(AssertionError) as cm:
148 core.publish_string('test', writer_name='odt',
149 settings_overrides=settings)
150 self.assertIn('`data` is no `str` instance', str(cm.exception))
153 class PublishDoctreeTestCase(unittest.TestCase, docutils.SettingsSpec):
155 settings_default_overrides = {
156 '_disable_config': True,
157 'warning_stream': docutils.io.NullOutput()}
159 def test_publish_doctree(self):
160 # Test `publish_doctree` and `publish_from_doctree`.
162 # Produce the document tree.
163 doctree = core.publish_doctree(
164 source=test_document, reader_name='standalone',
165 parser_name='restructuredtext', settings_spec=self,
166 settings_overrides={'expose_internals':
167 ['refnames', 'do_not_expose'],
168 'report_level': 5})
169 self.assertTrue(isinstance(doctree, nodes.document))
171 # Confirm that transforms have been applied (in this case, the
172 # DocTitle transform):
173 self.assertTrue(isinstance(doctree[0], nodes.title))
174 self.assertTrue(isinstance(doctree[1], nodes.paragraph))
175 # Confirm that the Messages transform has not yet been applied:
176 self.assertEqual(2, len(doctree))
178 # The `do_not_expose` attribute may not show up in the
179 # pseudoxml output because the expose_internals transform may
180 # not be applied twice.
181 doctree.do_not_expose = 'test'
182 # Write out the document:
183 output = core.publish_from_doctree(
184 doctree, writer_name='pseudoxml',
185 settings_spec=self,
186 settings_overrides={'expose_internals':
187 ['refnames', 'do_not_expose'],
188 'report_level': 1,
189 'output_encoding': 'unicode'})
190 self.assertEqual(exposed_pseudoxml_output, output)
192 # Test publishing parts using document as the source.
193 parts = core.publish_parts(
194 reader_name='doctree', source_class=docutils.io.DocTreeInput,
195 source=doctree, source_path='test', writer_name='html',
196 settings_spec=self)
197 self.assertTrue(isinstance(parts, dict))
199 def test_publish_pickle(self):
200 # Test publishing a document tree with pickling and unpickling.
202 # Produce the document tree.
203 doctree = core.publish_doctree(
204 source=test_document,
205 reader_name='standalone',
206 parser_name='restructuredtext',
207 settings_spec=self)
208 self.assertTrue(isinstance(doctree, nodes.document))
210 # Pickle the document. Note: if this fails, some unpickleable
211 # reference has been added somewhere within the document tree.
212 # If so, you need to fix that.
214 # Note: Please do not remove this test, this is an important
215 # requirement, applications will be built on the assumption
216 # that we can pickle the document.
218 # Remove the reporter and the transformer before pickling.
219 doctree.reporter = None
220 doctree.transformer = None
222 doctree_pickled = pickle.dumps(doctree)
223 self.assertTrue(isinstance(doctree_pickled, bytes))
224 del doctree
226 # Unpickle the document.
227 doctree_zombie = pickle.loads(doctree_pickled)
228 self.assertTrue(isinstance(doctree_zombie, nodes.document))
230 # Write out the document:
231 output = core.publish_from_doctree(doctree_zombie,
232 writer_name='pseudoxml',
233 settings_spec=self)
234 self.assertEqual(pseudoxml_output, output.decode())
237 if __name__ == '__main__':
238 unittest.main()