1 #! /usr/bin/env python3
4 # Author: David Goodger <goodger@python.org>
5 # Copyright: This module has been placed in the public domain.
8 Test module for utils/__init__.py.
11 from io
import StringIO
13 from pathlib
import Path
17 if __name__
== '__main__':
18 # prepend the "docutils root" to the Python library path
19 # so we import the local `docutils` package.
20 sys
.path
.insert(0, str(Path(__file__
).resolve().parents
[1]))
23 from docutils
import utils
, nodes
25 TEST_ROOT
= Path(__file__
).parent
# ./test/ from the docutils root
28 class ReporterTests(unittest
.TestCase
):
31 reporter
= utils
.Reporter('test data', 2, 4, stream
, 1)
35 self
.stream
.truncate()
37 def test_level0(self
):
38 sw
= self
.reporter
.system_message(0, 'debug output')
39 self
.assertEqual(sw
.pformat(), """\
40 <system_message level="0" source="test data" type="DEBUG">
44 self
.assertEqual(self
.stream
.getvalue(),
45 'test data:: (DEBUG/0) debug output\n')
47 def test_level1(self
):
48 sw
= self
.reporter
.system_message(1, 'a little reminder')
49 self
.assertEqual(sw
.pformat(), """\
50 <system_message level="1" source="test data" type="INFO">
54 self
.assertEqual(self
.stream
.getvalue(), '')
56 def test_level2(self
):
57 sw
= self
.reporter
.system_message(2, 'a warning')
58 self
.assertEqual(sw
.pformat(), """\
59 <system_message level="2" source="test data" type="WARNING">
63 self
.assertEqual(self
.stream
.getvalue(),
64 'test data:: (WARNING/2) a warning\n')
66 def test_level3(self
):
67 sw
= self
.reporter
.system_message(3, 'an error')
68 self
.assertEqual(sw
.pformat(), """\
69 <system_message level="3" source="test data" type="ERROR">
73 self
.assertEqual(self
.stream
.getvalue(),
74 'test data:: (ERROR/3) an error\n')
76 def test_level4(self
):
77 with self
.assertRaises(utils
.SystemMessage
):
78 self
.reporter
.system_message(
79 4, 'a severe error, raises an exception')
80 self
.assertEqual(self
.stream
.getvalue(), 'test data:: (SEVERE/4) '
81 'a severe error, raises an exception\n')
83 def test_unicode_message(self
):
84 sw
= self
.reporter
.system_message(0, 'mesidʒ')
85 self
.assertEqual(sw
.pformat(), """\
86 <system_message level="0" source="test data" type="DEBUG">
91 def test_unicode_message_from_exception(self
):
92 """Workaround for Python < 2.6 bug:
93 unicode(<exception instance>) uses __str__
94 and hence fails with unicode message"""
96 raise Exception('mesidʒ')
97 except Exception as err
:
98 sw
= self
.reporter
.system_message(0, err
)
99 self
.assertEqual(sw
.pformat(), """\
100 <system_message level="0" source="test data" type="DEBUG">
106 class QuietReporterTests(unittest
.TestCase
):
109 reporter
= utils
.Reporter('test data', 5, 5, stream
, 0)
113 self
.stream
.truncate()
115 def test_debug(self
):
116 sw
= self
.reporter
.debug('a debug message')
117 # None because debug is disabled.
118 self
.assertEqual(sw
, None)
119 self
.assertEqual(self
.stream
.getvalue(), '')
122 sw
= self
.reporter
.info('an informational message')
123 self
.assertEqual(sw
.pformat(), """\
124 <system_message level="1" source="test data" type="INFO">
126 an informational message
128 self
.assertEqual(self
.stream
.getvalue(), '')
130 def test_warning(self
):
131 sw
= self
.reporter
.warning('a warning')
132 self
.assertEqual(sw
.pformat(), """\
133 <system_message level="2" source="test data" type="WARNING">
137 self
.assertEqual(self
.stream
.getvalue(), '')
139 def test_error(self
):
140 sw
= self
.reporter
.error('an error')
141 self
.assertEqual(sw
.pformat(), """\
142 <system_message level="3" source="test data" type="ERROR">
146 self
.assertEqual(self
.stream
.getvalue(), '')
148 def test_severe(self
):
149 sw
= self
.reporter
.severe('a severe error')
150 self
.assertEqual(sw
.pformat(), """\
151 <system_message level="4" source="test data" type="SEVERE">
155 self
.assertEqual(self
.stream
.getvalue(), '')
158 class NameValueTests(unittest
.TestCase
):
160 def test_extract_name_value(self
):
161 with self
.assertRaises(utils
.NameValueError
):
162 utils
.extract_name_value('hello')
163 with self
.assertRaises(utils
.NameValueError
):
164 utils
.extract_name_value('=hello')
165 with self
.assertRaises(utils
.NameValueError
):
166 utils
.extract_name_value('hello=')
167 with self
.assertRaises(utils
.NameValueError
):
168 utils
.extract_name_value('hello="')
169 with self
.assertRaises(utils
.NameValueError
):
170 utils
.extract_name_value('hello="something')
171 with self
.assertRaises(utils
.NameValueError
):
172 utils
.extract_name_value('hello="something"else')
173 output
= utils
.extract_name_value(
174 """att1=val1 att2=val2 att3="value number '3'" att4=val4""")
175 self
.assertEqual(output
, [('att1', 'val1'), ('att2', 'val2'),
176 ('att3', "value number '3'"),
180 class ExtensionOptionTests(unittest
.TestCase
):
182 optionspec
= {'a': int, 'bbb': float, 'cdef': (lambda x
: x
),
183 'empty': (lambda x
: x
)}
185 def test_assemble_option_dict(self
):
186 input = utils
.extract_name_value('a=1 bbb=2.0 cdef=hol%s' % chr(224))
188 utils
.assemble_option_dict(input, self
.optionspec
),
189 {'a': 1, 'bbb': 2.0, 'cdef': ('hol%s' % chr(224))})
190 input = utils
.extract_name_value('a=1 b=2.0 c=hol%s' % chr(224))
191 with self
.assertRaises(KeyError):
192 utils
.assemble_option_dict(input, self
.optionspec
)
193 input = utils
.extract_name_value('a=1 bbb=two cdef=hol%s' % chr(224))
194 with self
.assertRaises(ValueError):
195 utils
.assemble_option_dict(input, self
.optionspec
)
197 def test_extract_extension_options(self
):
198 field_list
= nodes
.field_list()
199 field_list
+= nodes
.field(
200 '', nodes
.field_name('', 'a'),
201 nodes
.field_body('', nodes
.paragraph('', '1')))
202 field_list
+= nodes
.field(
203 '', nodes
.field_name('', 'bbb'),
204 nodes
.field_body('', nodes
.paragraph('', '2.0')))
205 field_list
+= nodes
.field(
206 '', nodes
.field_name('', 'cdef'),
207 nodes
.field_body('', nodes
.paragraph('', 'hol\u00e0')))
208 field_list
+= nodes
.field(
209 '', nodes
.field_name('', 'empty'), nodes
.field_body())
211 utils
.extract_extension_options(field_list
, self
.optionspec
),
215 with self
.assertRaises(KeyError):
216 utils
.extract_extension_options(field_list
, {})
217 field_list
+= nodes
.field(
218 '', nodes
.field_name('', 'cdef'),
219 nodes
.field_body('', nodes
.paragraph('', 'one'),
220 nodes
.paragraph('', 'two')))
221 with self
.assertRaises(utils
.BadOptionDataError
):
222 utils
.extract_extension_options(field_list
, self
.optionspec
)
223 field_list
[-1] = nodes
.field(
224 '', nodes
.field_name('', 'cdef bad'),
225 nodes
.field_body('', nodes
.paragraph('', 'no arguments')))
226 with self
.assertRaises(utils
.BadOptionError
):
227 utils
.extract_extension_options(field_list
, self
.optionspec
)
228 field_list
[-1] = nodes
.field(
229 '', nodes
.field_name('', 'cdef'),
230 nodes
.field_body('', nodes
.paragraph('', 'duplicate')))
231 with self
.assertRaises(utils
.DuplicateOptionError
):
232 utils
.extract_extension_options(field_list
, self
.optionspec
)
233 field_list
[-2] = nodes
.field(
234 '', nodes
.field_name('', 'unkown'),
235 nodes
.field_body('', nodes
.paragraph('', 'unknown')))
236 with self
.assertRaises(KeyError):
237 utils
.extract_extension_options(field_list
, self
.optionspec
)
240 class HelperFunctionTests(unittest
.TestCase
):
242 # Test conversion from `version information tuple` to a PEP 440 compliant
243 # Docutils version identifier.
244 # See 'Version Numbering' in docs/dev/policies.txt.
245 def test_version_identifier(self
):
246 release_0_14_final
= docutils
.VersionInfo(
247 major
=0, minor
=14, micro
=0,
248 releaselevel
='final', serial
=0, release
=True)
249 self
.assertEqual(utils
.version_identifier(release_0_14_final
), '0.14')
250 dev_0_15_beta
= docutils
.VersionInfo(
251 major
=0, minor
=15, micro
=0,
252 releaselevel
='beta', serial
=0, release
=False)
253 self
.assertEqual(utils
.version_identifier(dev_0_15_beta
), '0.15b.dev')
254 release_0_14_rc1
= docutils
.VersionInfo(
255 major
=0, minor
=14, micro
=0,
256 releaselevel
='candidate', serial
=1, release
=True)
257 self
.assertEqual(utils
.version_identifier(release_0_14_rc1
), '0.14rc1')
259 def test_implicit_version_identifier(self
):
261 utils
.version_identifier(docutils
.__version
_info
__),
262 utils
.version_identifier())
264 def test_normalize_language_tag(self
):
265 self
.assertEqual(utils
.normalize_language_tag('de'), ['de'])
266 self
.assertEqual(utils
.normalize_language_tag('de-AT'),
268 self
.assertEqual(utils
.normalize_language_tag('de-AT-1901'),
269 ['de-at-1901', 'de-at', 'de-1901', 'de'])
270 self
.assertEqual(utils
.normalize_language_tag('de-AT-1901-Latf'),
271 ['de-at-1901-latf', 'de-at-1901', 'de-at-latf',
272 'de-1901-latf', 'de-at', 'de-1901', 'de-latf', 'de'])
273 self
.assertEqual(utils
.normalize_language_tag('grc-ibycus-x-altquot'),
274 ['grc-ibycus-x-altquot', 'grc-ibycus',
275 'grc-x-altquot', 'grc'])
277 def test_xml_declaration(self
):
278 # default is no encoding declaration
279 self
.assertEqual(utils
.xml_declaration(), '<?xml version="1.0"?>\n')
280 # if an encoding is passed, declare it
281 self
.assertEqual(utils
.xml_declaration('ISO-8859-2'),
282 '<?xml version="1.0" encoding="ISO-8859-2"?>\n')
283 # ignore pseudo encoding name "unicode" introduced by
284 # `docutils.io.Output.encode()`
285 self
.assertEqual(utils
.xml_declaration('Unicode'),
286 '<?xml version="1.0"?>\n')
287 # ... non-regarding case
288 self
.assertEqual(utils
.xml_declaration('UNICODE'),
289 '<?xml version="1.0"?>\n')
290 # allow %s for later interpolation
291 # (used for part 'html_prolog', cf. docs/api/publisher.html)
292 self
.assertEqual(utils
.xml_declaration('%s'),
293 '<?xml version="1.0" encoding="%s"?>\n')
295 def test_column_width(self
):
296 self
.assertEqual(utils
.column_width('de'), 2)
297 self
.assertEqual(utils
.column_width('dâ'), 2) # pre-composed
298 self
.assertEqual(utils
.column_width('dâ'), 2) # combining
300 def test_decode_path(self
):
302 bytes_filename
= 'späm'.encode(sys
.getfilesystemencoding())
303 except UnicodeEncodeError:
304 bytes_filename
= b
'spam'
305 bytespath
= utils
.decode_path(bytes_filename
)
306 unipath
= utils
.decode_path('späm')
307 defaultpath
= utils
.decode_path(None)
308 if bytes_filename
!= b
'spam': # skip if ä cannot be encoded
309 self
.assertEqual(bytespath
, 'späm')
310 self
.assertEqual(unipath
, 'späm')
311 self
.assertEqual(defaultpath
, '')
312 self
.assertTrue(isinstance(bytespath
, str))
313 self
.assertTrue(isinstance(unipath
, str))
314 self
.assertTrue(isinstance(defaultpath
, str))
315 self
.assertRaises(ValueError, utils
.decode_path
, 13)
317 def test_relative_path(self
):
318 # Build and return a path to `target`, relative to `source`:
319 # Use '/' as path sep in result.
320 self
.assertEqual(utils
.relative_path('spam', 'spam'), '')
321 source
= os
.path
.join('häm', 'spam', 'fileA')
322 target
= os
.path
.join('häm', 'spam', 'fileB')
323 self
.assertEqual(utils
.relative_path(source
, target
), 'fileB')
324 source
= os
.path
.join('häm', 'spam', 'fileA')
325 target
= os
.path
.join('häm', 'fileB')
326 self
.assertEqual(utils
.relative_path(source
, target
), '../fileB')
327 source
= os
.path
.join('häm', 'fileA')
328 target
= os
.path
.join('..', 'spam', 'fileB')
329 self
.assertEqual(utils
.relative_path(source
, target
),
331 # if source is None, default to the cwd:
332 target
= os
.path
.join('eggs', 'fileB')
333 self
.assertEqual(utils
.relative_path(None, target
), 'eggs/fileB')
334 # If there is no common prefix, return the absolute path to `target`:
336 source
= '/foo/bar/fileA'
338 source
= r
'C:\foo\bar\fileA'
339 target
= os
.path
.join('eggs', 'fileB')
340 self
.assertEqual(utils
.relative_path(source
, target
),
341 os
.path
.abspath('eggs/fileB'))
342 # Correctly process characters outside the ASCII range:
343 self
.assertEqual(utils
.relative_path('spam', 'spam'), '')
344 source
= os
.path
.join('häm', 'spam', 'fileA')
345 target
= os
.path
.join('häm', 'spam', 'fileB')
346 self
.assertEqual(utils
.relative_path(source
, target
), 'fileB')
347 source
= os
.path
.join('häm', 'spam', 'fileA')
348 target
= os
.path
.join('häm', 'fileB')
349 self
.assertEqual(utils
.relative_path(source
, target
), '../fileB')
350 # if source is None, default to the cwd:
351 target
= os
.path
.join('eggs', 'fileB')
352 self
.assertEqual(utils
.relative_path(None, target
), 'eggs/fileB')
354 def test_find_file_in_dirs(self
):
355 # Search for file `path` in the sequence of directories `dirs`.
356 # Return the first expansion that matches an existing file.
357 dirs
= (os
.path
.join(TEST_ROOT
, 'nonex'),
359 os
.path
.join(TEST_ROOT
, '..'))
360 result
= utils
.find_file_in_dirs('alltests.py', dirs
)
361 expected
= os
.path
.join(TEST_ROOT
, 'alltests.py').replace('\\', '/')
362 self
.assertEqual(expected
, result
)
363 result
= utils
.find_file_in_dirs('HISTORY.txt', dirs
)
364 expected
= (TEST_ROOT
/ '..' / 'HISTORY.txt').as_posix()
365 self
.assertEqual(expected
, result
)
366 # normalize for second check
367 self
.assertTrue(os
.path
.relpath(result
, TEST_ROOT
).startswith('..'),
368 'HISTORY.txt not found in "..".')
369 # Return `path` if the file exists in the cwd or if there is no match
370 self
.assertEqual(utils
.find_file_in_dirs('gibts/nicht.txt', dirs
),
373 # samples for the (un)escaping tests:
374 escaped
= r
'escapes: \*one, \\*two, \\\*three in\side no\ space' + '\\'
375 nulled
= ('escapes: \x00*one, \x00\\*two, \x00\\\x00*three'
376 + ' in\x00side no\x00 space\x00')
377 unescaped
= r
'escapes: *one, \*two, \*three inside nospace'
379 def test_escape2null(self
):
380 nulled
= utils
.escape2null(self
.escaped
)
381 self
.assertEqual(nulled
, self
.nulled
)
383 def test_unescape(self
):
384 unescaped
= utils
.unescape(self
.nulled
)
385 self
.assertEqual(unescaped
, self
.unescaped
)
386 restored
= utils
.unescape(self
.nulled
, restore_backslashes
=True)
387 self
.assertEqual(restored
, self
.escaped
)
390 class StylesheetFunctionTests(unittest
.TestCase
):
392 stylesheet_dirs
= [TEST_ROOT
, os
.path
.join(TEST_ROOT
, 'data')]
394 def test_get_stylesheet_list_stylesheet_path(self
):
395 # look for stylesheets in stylesheet_dirs
396 self
.stylesheet
= None
397 self
.stylesheet_path
= 'ham.css, missing.css'
399 ham_css
= os
.path
.join(TEST_ROOT
, 'data', 'ham.css').replace('\\', '/')
400 self
.assertEqual(utils
.get_stylesheet_list(self
),
401 [ham_css
, 'missing.css'])
403 def test_get_stylesheet_list_stylesheet(self
):
404 # use stylesheet paths verbatim
405 self
.stylesheet
= 'ham.css, missing.css'
406 self
.stylesheet_path
= None
408 self
.assertEqual(utils
.get_stylesheet_list(self
),
409 ['ham.css', 'missing.css'])
411 def test_get_stylesheet_list_conflict(self
):
412 # settings "stylesheet_path" and "stylesheet"
413 # must not be used together
414 self
.stylesheet
= 'ham.css, missing.css'
415 self
.stylesheet_path
= 'man.css, miss2.css'
416 with self
.assertRaises(AssertionError):
417 utils
.get_stylesheet_list(self
)
420 if __name__
== '__main__':