test_tools.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. """
  2. Python Markdown
  3. A Python implementation of John Gruber's Markdown.
  4. Documentation: https://python-markdown.github.io/
  5. GitHub: https://github.com/Python-Markdown/markdown/
  6. PyPI: https://pypi.org/project/Markdown/
  7. Started by Manfred Stienstra (http://www.dwerg.net/).
  8. Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
  9. Currently maintained by Waylan Limberg (https://github.com/waylan),
  10. Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
  11. Copyright 2007-2018 The Python Markdown Project (v. 1.7 and later)
  12. Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
  13. Copyright 2004 Manfred Stienstra (the original version)
  14. License: BSD (see LICENSE.md for details).
  15. """
  16. import os
  17. import unittest
  18. import textwrap
  19. from . import markdown
  20. try:
  21. import tidylib
  22. except ImportError:
  23. tidylib = None
  24. __all__ = ['TestCase', 'LegacyTestCase', 'Kwargs']
  25. class TestCase(unittest.TestCase):
  26. """
  27. A unittest.TestCase subclass with helpers for testing Markdown output.
  28. Define `default_kwargs` as a dict of keywords to pass to Markdown for each
  29. test. The defaults can be overridden on individual tests.
  30. The `assertMarkdownRenders` method accepts the source text, the expected
  31. output, and any keywords to pass to Markdown. The `default_kwargs` are used
  32. except where overridden by `kwargs`. The ouput and expected ouput are passed
  33. to `TestCase.assertMultiLineEqual`. An AssertionError is raised with a diff
  34. if the actual output does not equal the expected output.
  35. The `dedent` method is available to dedent triple-quoted strings if
  36. necessary.
  37. In all other respects, behaves as unittest.TestCase.
  38. """
  39. default_kwargs = {}
  40. def assertMarkdownRenders(self, source, expected, **kwargs):
  41. """
  42. Test that source Markdown text renders to expected output with given keywords.
  43. """
  44. kws = self.default_kwargs.copy()
  45. kws.update(kwargs)
  46. output = markdown(source, **kws)
  47. self.assertMultiLineEqual(output, expected)
  48. def dedent(self, text):
  49. """
  50. Dedent text.
  51. """
  52. # TODO: If/when actual output ends with a newline, then use:
  53. # return textwrap.dedent(text.strip('/n'))
  54. return textwrap.dedent(text).strip()
  55. #########################
  56. # Legacy Test Framework #
  57. #########################
  58. class Kwargs(dict):
  59. """ A dict like class for holding keyword arguments. """
  60. pass
  61. def _normalize_whitespace(text):
  62. """ Normalize whitespace for a string of html using tidylib. """
  63. output, errors = tidylib.tidy_fragment(text, options={
  64. 'drop_empty_paras': 0,
  65. 'fix_backslash': 0,
  66. 'fix_bad_comments': 0,
  67. 'fix_uri': 0,
  68. 'join_styles': 0,
  69. 'lower_literals': 0,
  70. 'merge_divs': 0,
  71. 'output_xhtml': 1,
  72. 'quote_ampersand': 0,
  73. 'newline': 'LF'
  74. })
  75. return output
  76. class LegacyTestMeta(type):
  77. def __new__(cls, name, bases, dct):
  78. def generate_test(infile, outfile, normalize, kwargs):
  79. def test(self):
  80. with open(infile, encoding="utf-8") as f:
  81. input = f.read()
  82. with open(outfile, encoding="utf-8") as f:
  83. # Normalize line endings
  84. # (on Windows, git may have altered line endings).
  85. expected = f.read().replace("\r\n", "\n")
  86. output = markdown(input, **kwargs)
  87. if tidylib and normalize:
  88. expected = _normalize_whitespace(expected)
  89. output = _normalize_whitespace(output)
  90. elif normalize:
  91. self.skipTest('Tidylib not available.')
  92. self.assertMultiLineEqual(output, expected)
  93. return test
  94. location = dct.get('location', '')
  95. exclude = dct.get('exclude', [])
  96. normalize = dct.get('normalize', False)
  97. input_ext = dct.get('input_ext', '.txt')
  98. output_ext = dct.get('output_ext', '.html')
  99. kwargs = dct.get('default_kwargs', Kwargs())
  100. if os.path.isdir(location):
  101. for file in os.listdir(location):
  102. infile = os.path.join(location, file)
  103. if os.path.isfile(infile):
  104. tname, ext = os.path.splitext(file)
  105. if ext == input_ext:
  106. outfile = os.path.join(location, tname + output_ext)
  107. tname = tname.replace(' ', '_').replace('-', '_')
  108. kws = kwargs.copy()
  109. if tname in dct:
  110. kws.update(dct[tname])
  111. test_name = 'test_%s' % tname
  112. if tname not in exclude:
  113. dct[test_name] = generate_test(infile, outfile, normalize, kws)
  114. else:
  115. dct[test_name] = unittest.skip('Excluded')(lambda: None)
  116. return type.__new__(cls, name, bases, dct)
  117. class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta):
  118. """
  119. A `unittest.TestCase` subclass for running Markdown's legacy file-based tests.
  120. A subclass should define various properties which point to a directory of
  121. text-based test files and define various behaviors/defaults for those tests.
  122. The following properties are supported:
  123. location: A path to the directory fo test files. An absolute path is preferred.
  124. exclude: A list of tests to exclude. Each test name should comprise the filename
  125. without an extension.
  126. normalize: A boolean value indicating if the HTML should be normalized.
  127. Default: `False`.
  128. input_ext: A string containing the file extension of input files. Default: `.txt`.
  129. ouput_ext: A string containing the file extension of expected output files.
  130. Default: `html`.
  131. default_kwargs: A `Kwargs` instance which stores the default set of keyword
  132. arguments for all test files in the directory.
  133. In addition, properties can be defined for each individual set of test files within
  134. the directory. The property should be given the name of the file without the file
  135. extension. Any spaces and dashes in the filename should be replaced with
  136. underscores. The value of the property should be a `Kwargs` instance which
  137. contains the keyword arguments that should be passed to `Markdown` for that
  138. test file. The keyword arguments will "update" the `default_kwargs`.
  139. When the class instance is created, it will walk the given directory and create
  140. a separate unitttest for each set of test files using the naming scheme:
  141. `test_filename`. One unittest will be run for each set of input and output files.
  142. """
  143. pass