4 # Author: Martin Blais <blais@furius.ca>
5 # Copyright: This module has been placed in the public domain.
8 Test the `Publisher` facade and the ``publish_*`` convenience functions.
12 from pathlib
import Path
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]))
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')
31 This is a test document with a broken reference: nonexistent_
33 pseudoxml_output
= """\
34 <document ids="test-document" names="test\\ document" source="<string>" title="Test Document">
38 This is a test document with a broken reference: \n\
39 <problematic ids="problematic-1" refid="system-message-1">
41 <section classes="system-messages">
43 Docutils System Messages
44 <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="4" source="<string>" type="ERROR">
46 Unknown target name: "nonexistent".
48 exposed_pseudoxml_output
= """\
49 <document ids="test-document" internal:refnames="{'nonexistent': [<reference: <#text: 'nonexistent'>>]}" names="test\\ document" source="<string>" title="Test Document">
53 This is a test document with a broken reference: \n\
54 <problematic ids="problematic-1" refid="system-message-1">
56 <section classes="system-messages">
58 Docutils System Messages
59 <system_message backrefs="problematic-1" ids="system-message-1" level="3" line="4" source="<string>" type="ERROR">
61 Unknown target name: "nonexistent".
65 class PublisherTests(unittest
.TestCase
):
67 settings
= {'_disable_config': True,
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'),
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
)
112 expected
= ('<document source="<string>">\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'
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'],
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',
186 settings_overrides
={'expose_internals':
187 ['refnames', 'do_not_expose'],
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',
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',
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
))
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',
234 self
.assertEqual(pseudoxml_output
, output
.decode())
237 if __name__
== '__main__':